New owncloud propagator that skip the vio abstraction layer

The vio abstraction layer within csync is inneficient for the owncloud
use case because not all calls maps well to the POSIX interface. We can
be much more efficient by doing exactly what we need.

Also, this will allow us to scedule better the calls and possibly to use
threads.
This commit is contained in:
Olivier Goffart 2013-05-03 19:11:00 +02:00
parent 105c76c055
commit e1e8842548
9 changed files with 665 additions and 25 deletions

View file

@ -64,6 +64,7 @@ endif()
find_package(Sphinx)
find_package(PdfLatex)
find_package(QtKeychain)
find_package(Neon)
set(WITH_CSYNC CSYNC_FOUND)
set(WITH_QTKEYCHAIN ${QTKEYCHAIN_FOUND})

View file

@ -0,0 +1,73 @@
# - Try to find Neon
# Once done this will define
#
# NEON_FOUND - system has Neon
# NEON_INCLUDE_DIRS - the Neon include directory
# NEON_LIBRARIES - Link these to use Neon
# NEON_DEFINITIONS - Compiler switches required for using Neon
#
# Copyright (c) 2011 Andreas Schneider <asn@cryptomilk.org>
#
# Redistribution and use is allowed according to the terms of the New
# BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_check_modules(_NEON neon)
endif (PKG_CONFIG_FOUND)
include(GNUInstallDirs)
find_path(NEON_INCLUDE_DIRS
NAMES
neon/ne_basic.h
HINTS
${_NEON_INCLUDEDIR}
${CMAKE_INSTALL_INCLUDEDIR}
)
find_library(NEON_LIBRARIES
NAMES
neon
HINTS
${_NEON_LIBDIR}
${CMAKE_INSTALL_LIBDIR}
${CMAKE_INSTALL_PREFIX}/lib
${CMAKE_INSTALL_PREFIX}/lib64
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Neon DEFAULT_MSG NEON_LIBRARIES NEON_INCLUDE_DIRS)
# show the NEON_INCLUDE_DIRS and NEON_LIBRARIES variables only in the advanced view
mark_as_advanced(NEON_INCLUDE_DIRS NEON_LIBRARIES)
# Check if neon was compiled with LFS support, if so, the NE_LFS variable has to
# be defined in the owncloud module.
# If neon was not compiled with LFS its also ok since the underlying system
# than probably supports large files anyway.
IF( CMAKE_FIND_ROOT_PATH )
FIND_PROGRAM( NEON_CONFIG_EXECUTABLE NAMES neon-config HINTS ${CMAKE_FIND_ROOT_PATH}/bin )
ELSE( CMAKE_FIND_ROOT_PATH )
FIND_PROGRAM( NEON_CONFIG_EXECUTABLE NAMES neon-config )
ENDIF( CMAKE_FIND_ROOT_PATH )
IF ( NEON_CONFIG_EXECUTABLE )
MESSAGE(STATUS "neon-config executable: ${NEON_CONFIG_EXECUTABLE}")
# neon-config --support lfs
EXECUTE_PROCESS( COMMAND ${NEON_CONFIG_EXECUTABLE} "--support" "lfs"
RESULT_VARIABLE LFS
OUTPUT_STRIP_TRAILING_WHITESPACE )
IF (LFS EQUAL 0)
MESSAGE(STATUS "libneon has been compiled with LFS support")
SET(NEON_WITH_LFS 1 PARENT_SCOPE)
ELSE (LFS EQUAL 0)
MESSAGE(STATUS "libneon has not been compiled with LFS support, rely on OS")
ENDIF (LFS EQUAL 0)
ELSE ( NEON_CONFIG_EXECUTABLE )
MESSAGE(STATUS, "neon-config could not be found.")
ENDIF ( NEON_CONFIG_EXECUTABLE )

View file

@ -73,6 +73,7 @@ set(libsync_SRCS
mirall/csyncfolder.cpp
mirall/owncloudfolder.cpp
mirall/csyncthread.cpp
mirall/owncloudpropagator.cpp
mirall/fileutils.cpp
mirall/theme.cpp
mirall/owncloudtheme.cpp
@ -89,6 +90,7 @@ set(libsync_HEADERS
mirall/csyncfolder.h
mirall/owncloudfolder.h
mirall/csyncthread.h
mirall/owncloudpropagator.h
mirall/owncloudinfo.h
mirall/credentialstore.h
mirall/logger.h
@ -111,10 +113,25 @@ ENDIF()
qt4_wrap_cpp(syncMoc ${libsync_HEADERS})
IF( DEFINED CSYNC_BUILD_PATH )
IF(WIN32)
SET(HTTPBF_LIBRARY ${CSYNC_BUILD_PATH}/src/httpbf/libhttpbflib.dll)
ELSEIF( APPLE )
SET(HTTPBF_LIBRARY ${CSYNC_BUILD_PATH}/src/httpbf/libhttpbflib.a)
ELSE()
SET(HTTPBF_LIBRARY ${CSYNC_BUILD_PATH}/src/httpbf/libhttpbflib.a)
ENDIF()
ELSE()
FIND_LIBRARY(HTTPBF_LIBRARY NAMES httpbflib HINTS $ENV{CSYNC_DIR})
ENDIF()
list(APPEND libsync_LINK_TARGETS
${QT_LIBRARIES}
${CSYNC_LIBRARY}
dl
${NEON_LIBRARIES}
${HTTPBF_LIBRARY}
)
if(QTKEYCHAIN_FOUND)
@ -122,6 +139,8 @@ if(QTKEYCHAIN_FOUND)
include_directories(${QTKEYCHAIN_INCLUDE_DIR})
endif()
add_library(owncloudsync SHARED ${libsync_SRCS} ${syncMoc})
set_target_properties( owncloudsync PROPERTIES COMPILE_DEFINITIONS OWNCLOUD_CLIENT)
set_target_properties( owncloudsync PROPERTIES
@ -184,7 +203,7 @@ if( UNIX AND NOT APPLE)
endif()
# csync is required.
include_directories(${CSYNC_INCLUDE_DIR}/csync ${CSYNC_INCLUDE_DIR} ${CSYNC_BUILD_PATH}/src)
include_directories(${CSYNC_INCLUDE_DIR}/csync ${CSYNC_INCLUDE_DIR} ${CSYNC_INCLUDE_DIR}/httpbf/src ${CSYNC_BUILD_PATH}/src)
include_directories(${3rdparty_INC})
qt4_wrap_cpp(mirallMoc ${mirall_HEADERS})
@ -236,6 +255,7 @@ endif(NOT WIN32)
target_link_libraries(mirall ${QT_LIBRARIES} )
target_link_libraries(mirall owncloudsync)
target_link_libraries(mirall ${CSYNC_LIBRARY})
target_link_libraries(mirall ${NEON_LIBRARIES})
set_target_properties( mirall PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} )
@ -278,6 +298,7 @@ set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
target_link_libraries( ${APPLICATION_EXECUTABLE} ${QT_LIBRARIES} )
target_link_libraries( ${APPLICATION_EXECUTABLE} owncloudsync )
target_link_libraries( ${APPLICATION_EXECUTABLE} ${CSYNC_LIBRARY} )
target_link_libraries( ${APPLICATION_EXECUTABLE} ${NEON_LIBRARIES} )
install(TARGETS ${APPLICATION_EXECUTABLE}
RUNTIME DESTINATION bin

View file

@ -18,6 +18,7 @@
#include "mirall/theme.h"
#include "mirall/logger.h"
#include "mirall/owncloudinfo.h"
#include "owncloudpropagator.h"
#ifdef Q_OS_WIN
#include <windows.h>
@ -46,9 +47,11 @@ namespace Mirall {
QMutex CSyncThread::_mutex;
QMutex CSyncThread::_syncMutex;
CSyncThread::CSyncThread(CSYNC *csync)
CSyncThread::CSyncThread(CSYNC *csync, const QString &localPath, const QString &remotePath)
{
_mutex.lock();
_localPath = localPath;
_remotePath = remotePath;
_csync_ctx = csync;
_mutex.unlock();
}
@ -186,6 +189,8 @@ int CSyncThread::treewalkFile( TREE_WALK_FILE *file, bool remote )
item._file = QString::fromUtf8( file->path );
item._instruction = file->instruction;
item._dir = SyncFileItem::None;
item._isDirectory = file->type == CSYNC_FTW_TYPE_DIR;
item._modtime = file->modtime;
SyncFileItem::Direction dir;
@ -237,19 +242,29 @@ int CSyncThread::treewalkFile( TREE_WALK_FILE *file, bool remote )
int CSyncThread::treewalkError(TREE_WALK_FILE* file)
{
SyncFileItem item;
item._file= QString::fromUtf8(file->path);
int indx = _syncedItems.indexOf(item);
if ( indx == -1 )
if (file->instruction == CSYNC_INSTRUCTION_IGNORE || file->instruction == CSYNC_INSTRUCTION_NONE)
return 0;
if( file &&
file->instruction == CSYNC_INSTRUCTION_STAT_ERROR ||
SyncFileItem item;
item._file= QString::fromUtf8(file->path);
QHash<QString, Action>::const_iterator action = performedActions.constFind(item._file);
if (action != performedActions.constEnd()) {
file->instruction = action->instruction;
if (!action->etag.isNull()) {
file->md5 = action->etag.constData();
}
}
if( file->instruction == CSYNC_INSTRUCTION_STAT_ERROR ||
file->instruction == CSYNC_INSTRUCTION_ERROR ) {
_mutex.lock();
_syncedItems[indx]._instruction = file->instruction;
_mutex.unlock();
QMutexLocker locker(&_mutex);
SyncFileItemVector::iterator it = qBinaryFind(_syncedItems.begin(), _syncedItems.end(), item);
if ( it == _syncedItems.end())
return 0;
it->_instruction = file->instruction;
}
return 0;
@ -336,13 +351,44 @@ void CSyncThread::startSync()
qDebug() << "Error in remote treewalk.";
}
qSort(_syncedItems);
if (_needsUpdate)
emit(started());
if( csync_propagate(_csync_ctx) < 0 ) {
handleSyncError(_csync_ctx, "cysnc_reconcile");
return;
ne_session_s *session = 0;
// that call to set property actually is a get which will return the session
csync_set_module_property(_csync_ctx, "get_dav_session", &session);
Q_ASSERT(session);
QString lastDeleted;
OwncloudPropagator propagator(session, _localPath, _remotePath);
foreach (const SyncFileItem &item , _syncedItems) {
Action a;
if (!lastDeleted.isEmpty() && item._file.startsWith(lastDeleted)
&& item._instruction == CSYNC_INSTRUCTION_REMOVE) {
a.instruction = CSYNC_INSTRUCTION_DELETED;
performedActions.insert(item._file, a);
continue;
}
propagator.etag.clear(); // FIXME : set to the right one
a.instruction = propagator.propagate(item);
if (a.instruction == CSYNC_INSTRUCTION_DELETED) {
lastDeleted = item._file;
} else {
lastDeleted.clear();
}
a.etag = propagator.etag;
performedActions.insert(item._file, a);
//TODO record errors and process;
}
// if( csync_propagate(_csync_ctx) < 0 ) {
// handleSyncError(_csync_ctx, "cysnc_reconcile");
// return;
// }
if( walkOk ) {
if( csync_walk_local_tree(_csync_ctx, &walkFinalize, 0) < 0 ||
@ -367,5 +413,4 @@ void CSyncThread::progress(const char *remote_url, enum csync_notify_type_e kind
}
}
}

View file

@ -34,8 +34,16 @@ namespace Mirall {
class CSyncThread : public QObject
{
Q_OBJECT
// Keep the actions that have been performed, in order to update the db
struct Action {
QByteArray etag;
csync_instructions_e instruction;
};
QHash<QString, Action> performedActions;
public:
CSyncThread(CSYNC *);
CSyncThread(CSYNC *, const QString &localPath, const QString &remotePath);
~CSyncThread();
QString csyncErrorToString( CSYNC_ERROR_CODE, const char * );
@ -71,13 +79,15 @@ private:
static int walkFinalize(TREE_WALK_FILE*, void* );
static QMutex _mutex;
static QMutex _syncMutex;
SyncFileItemVector _syncedItems;
CSYNC *_csync_ctx;
bool _needsUpdate;
QString _localPath;
QString _remotePath;
friend class CSyncRunScopeHelper;
};

View file

@ -262,7 +262,7 @@ void ownCloudFolder::startSync(const QStringList &pathList)
qDebug() << "*** Start syncing";
_thread = new QThread(this);
_csync = new CSyncThread( _csync_ctx );
_csync = new CSyncThread( _csync_ctx, path(), QUrl(Folder::secondPath()).path() );
_csync->moveToThread(_thread);
qRegisterMetaType<SyncFileItemVector>("SyncFileItemVector");

View file

@ -0,0 +1,419 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "owncloudpropagator.h"
#include <httpbf.h>
#include <qfile.h>
#include <qdir.h>
#include <qdiriterator.h>
#include <qtemporaryfile.h>
#include <qabstractfileengine.h>
#include <neon/ne_basic.h>
#include <neon/ne_socket.h>
#include <neon/ne_session.h>
#include <neon/ne_request.h>
#include <neon/ne_props.h>
#include <neon/ne_auth.h>
#include <neon/ne_dates.h>
#include <neon/ne_compress.h>
#include <neon/ne_redirect.h>
namespace Mirall {
/* Helper for QScopedPointer<>, to be used as the deleter.
* QScopePointer will call the right overload of cleanup for the pointer it holds
*/
struct ScopedPointerHelpers {
static inline void cleanup(hbf_transfer_t *pointer) { if (pointer) hbf_free_transfer(pointer); }
static inline void cleanup(ne_request *pointer) { if (pointer) ne_request_destroy(pointer); }
static inline void cleanup(ne_decompress *pointer) { if (pointer) ne_decompress_destroy(pointer); }
// static inline void cleanup(ne_propfind_handler *pointer) { if (pointer) ne_propfind_destroy(pointer); }
};
csync_instructions_e OwncloudPropagator::propagate(const SyncFileItem &item)
{
switch(item._instruction) {
case CSYNC_INSTRUCTION_REMOVE:
return item._dir == SyncFileItem::Down ? localRemove(item) : remoteRemove(item);
case CSYNC_INSTRUCTION_NEW:
if (item._isDirectory) {
return item._dir == SyncFileItem::Down ? localMkdir(item) : remoteMkdir(item);
} //fall trough
case CSYNC_INSTRUCTION_SYNC:
if (item._isDirectory) {
return CSYNC_INSTRUCTION_UPDATED; //FIXME
} else {
return item._dir == SyncFileItem::Down ? downloadFile(item) : uploadFile(item);
}
case CSYNC_INSTRUCTION_CONFLICT:
return downloadFile(item);
case CSYNC_INSTRUCTION_RENAME:
return remoteRename(item);
default:
return item._instruction;
}
}
// Code copied from Qt5's QDir::removeRecursively
static bool removeRecursively(const QString &path)
{
bool success = true;
QDirIterator di(path, QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot);
while (di.hasNext()) {
di.next();
const QFileInfo& fi = di.fileInfo();
bool ok;
if (fi.isDir() && !fi.isSymLink())
ok = removeRecursively(di.filePath()); // recursive
else
ok = QFile::remove(di.filePath());
if (!ok)
success = false;
}
if (success)
success = QDir().rmdir(path);
return success;
}
csync_instructions_e OwncloudPropagator::localRemove(const SyncFileItem& item)
{
QString filename = _localDir + item._file;
if (item._isDirectory) {
if (!QDir(filename).exists() || removeRecursively(filename))
return CSYNC_INSTRUCTION_DELETED;
} else {
QFile file(filename);
if (!file.exists() || file.remove())
return CSYNC_INSTRUCTION_DELETED;
errorString = file.errorString();
}
//FIXME: we should update the md5
etag.clear();
//FIXME: we should update the mtime
return CSYNC_INSTRUCTION_NONE; // not ERROR so it is still written to the database
}
csync_instructions_e OwncloudPropagator::localMkdir(const SyncFileItem &item)
{
QDir d;
if (!d.mkpath(_localDir + item._file)) {
//FIXME: errorString
return CSYNC_INSTRUCTION_ERROR;
}
return CSYNC_INSTRUCTION_UPDATED;
}
csync_instructions_e OwncloudPropagator::remoteRemove(const SyncFileItem &item)
{
QScopedPointer<char, QScopedPointerPodDeleter> uri(ne_path_escape((_remoteDir + item._file).toUtf8()));
int rc = ne_delete(_session, uri.data());
if (rc != NE_OK) {
updateErrorFromSession();
return CSYNC_INSTRUCTION_ERROR;
}
return CSYNC_INSTRUCTION_DELETED;
}
csync_instructions_e OwncloudPropagator::remoteMkdir(const SyncFileItem &item)
{
QScopedPointer<char, QScopedPointerPodDeleter> uri(ne_path_escape((_remoteDir + item._file).toUtf8()));
int rc = ne_mkcol(_session, uri.data());
if (rc != NE_OK) {
updateErrorFromSession();
/* Special for mkcol: it returns 405 if the directory already exists.
* Ignre that error */
if (errorCode != 405)
return CSYNC_INSTRUCTION_ERROR;
}
return CSYNC_INSTRUCTION_UPDATED;
}
csync_instructions_e OwncloudPropagator::uploadFile(const SyncFileItem &item)
{
QFile file(_localDir + item._file);
if (!file.open(QIODevice::ReadOnly)) {
errorString = file.errorString();
return CSYNC_INSTRUCTION_ERROR;
}
QScopedPointer<char, QScopedPointerPodDeleter> uri(ne_path_escape((_remoteDir + item._file).toUtf8()));
//TODO: FIXME: check directory
bool finished = true;
int attempts = 0;
/*
* do ten tries to upload the file chunked. Check the file size and mtime
* before submitting a chunk and after having submitted the last one.
* If the file has changed, retry.
*/
do {
Hbf_State state = HBF_SUCCESS;
QScopedPointer<hbf_transfer_t, ScopedPointerHelpers> trans(hbf_init_transfer(uri.data()));
finished = true;
Q_ASSERT(trans);
state = hbf_splitlist(trans.data(), file.handle());
//FIXME TODO
#if 0
/* Reuse chunk info that was stored in database if existing. */
if (dav_session.chunk_info && dav_session.chunk_info->transfer_id) {
DEBUG_WEBDAV("Existing chunk info %d %d ", dav_session.chunk_info->start_id, dav_session.chunk_info->transfer_id);
trans->start_id = dav_session.chunk_info->start_id;
trans->transfer_id = dav_session.chunk_info->transfer_id;
}
if (state == HBF_SUCCESS && _progresscb) {
ne_set_notifier(dav_session.ctx, ne_notify_status_cb, write_ctx);
_progresscb(write_ctx->url, CSYNC_NOTIFY_START_UPLOAD, 0 , 0, dav_session.userdata);
}
#endif
if( state == HBF_SUCCESS ) {
//chunked_total_size = trans->stat_size;
/* Transfer all the chunks through the HTTP session using PUT. */
state = hbf_transfer( _session, trans.data(), "PUT" );
}
/* Handle errors. */
if ( state != HBF_SUCCESS ) {
/* If the source file changed during submission, lets try again */
if( state == HBF_SOURCE_FILE_CHANGE ) {
if( attempts++ < 30 ) { /* FIXME: How often do we want to try? */
finished = false; /* make it try again from scratch. */
qDebug("SOURCE file has changed during upload, retry #%d in two seconds!", attempts);
sleep(2);
}
}
if( finished ) {
errorString = hbf_error_string(state);
// errorCode = hbf_fail_http_code(trans);
// if (dav_session.chunk_info) {
// dav_session.chunk_info->start_id = trans->start_id;
// dav_session.chunk_info->transfer_id = trans->transfer_id;
// }
return CSYNC_INSTRUCTION_ERROR;
}
}
} while( !finished );
// if (_progresscb) {
// ne_set_notifier(dav_session.ctx, 0, 0);
// _progresscb(write_ctx->url, rc != 0 ? CSYNC_NOTIFY_ERROR :
// CSYNC_NOTIFY_FINISHED_UPLOAD, error_code,
// (long long)(error_string), dav_session.userdata);
// }
//Update mtime;
QByteArray modtime = QByteArray::number(qlonglong(item._modtime));
ne_propname pname;
pname.nspace = "DAV:";
pname.name = "lastmodified";
ne_proppatch_operation ops[2];
ops[0].name = &pname;
ops[0].type = ne_propset;
ops[0].value = modtime.constData();
ops[1].name = NULL;
int rc = ne_proppatch( _session, uri.data(), ops );
if( rc != NE_OK ) {
//FIXME
}
// get the etag
QScopedPointer<ne_request, ScopedPointerHelpers> req(ne_request_create(_session, "HEAD", uri.data()));
int neon_stat = ne_request_dispatch(req.data());
if (neon_stat != NE_OK) {
//FIXME
} else {
const char *header = ne_get_response_header(req.data(), "etag");
if(header && header [0] == '"' && header[ strlen(header)-1] == '"') {
etag = QByteArray(header + 1, strlen(header)-2);
} else {
etag = header;
}
}
return CSYNC_INSTRUCTION_UPDATED;
}
struct DownloadContext {
QFile *file;
QScopedPointer<ne_decompress, ScopedPointerHelpers> decompress;
explicit DownloadContext(QFile *file) : file(file) {}
static int content_reader(void *userdata, const char *buf, size_t len)
{
DownloadContext *writeCtx = reinterpret_cast<DownloadContext *>(userdata);
size_t written = 0;
if(buf) {
written = writeCtx->file->write(buf, len);
if( len != written ) {
qDebug("WRN: content_reader wrote wrong num of bytes: %zu, %zu", len, written);
}
return NE_OK;
}
return NE_ERROR;
}
/*
* This hook is called after the response is here from the server, but before
* the response body is parsed. It decides if the response is compressed and
* if it is it installs the compression reader accordingly.
* If the response is not compressed, the normal response body reader is installed.
*/
static void install_content_reader( ne_request *req, void *userdata, const ne_status *status )
{
DownloadContext *writeCtx = reinterpret_cast<DownloadContext *>(userdata);
Q_UNUSED(status);
if( !writeCtx ) {
qDebug("Error: install_content_reader called without valid write context!");
return;
}
const char *enc = ne_get_response_header( req, "Content-Encoding" );
qDebug("Content encoding ist <%s> with status %d", enc ? enc : "empty",
status ? status->code : -1 );
if( enc == QLatin1String("gzip") ) {
writeCtx->decompress.reset(ne_decompress_reader( req, ne_accept_2xx,
content_reader, /* reader callback */
writeCtx )); /* userdata */
} else {
ne_add_response_body_reader( req, ne_accept_2xx,
content_reader,
(void*) writeCtx );
}
//enc = ne_get_response_header( req, "ETag" );
//FIXME: save ETAG
// if (enc && *enc) {
// SAFE_FREE(_id_cache.uri);
// SAFE_FREE(_id_cache.id);
// _id_cache.uri = c_strdup(writeCtx->url);
// _id_cache.id = c_strdup(enc);
// }
}
};
csync_instructions_e OwncloudPropagator::downloadFile(const SyncFileItem &item)
{
QTemporaryFile tmpFile(_localDir + item._file);
if (!tmpFile.open()) {
errorString = tmpFile.errorString();
return CSYNC_INSTRUCTION_ERROR;
}
/* actually do the request */
int retry = 0;
// if (_progresscb) {
// ne_set_notifier(dav_session.ctx, ne_notify_status_cb, write_ctx);
// _progresscb(write_ctx->url, CSYNC_NOTIFY_START_DOWNLOAD, 0 , 0, dav_session.userdata);
// }
QScopedPointer<char, QScopedPointerPodDeleter> uri(ne_path_escape((_remoteDir + item._file).toUtf8()));
DownloadContext writeCtx(&tmpFile);
do {
QScopedPointer<ne_request, ScopedPointerHelpers> req(ne_request_create(_session, "GET", uri.data()));
/* Allow compressed content by setting the header */
ne_add_request_header( req.data(), "Accept-Encoding", "gzip" );
if (tmpFile.size() > 0) {
char brange[64];
ne_snprintf(brange, sizeof brange, "bytes=%lld-", (long long) tmpFile.size());
ne_add_request_header(req.data(), "Range", brange);
ne_add_request_header(req.data(), "Accept-Ranges", "bytes");
qDebug("Retry with range %s", brange);
}
/* hook called before the content is parsed to set the correct reader,
* either the compressed- or uncompressed reader.
*/
ne_hook_post_headers( _session, DownloadContext::install_content_reader, &writeCtx);
int neon_stat = ne_request_dispatch(req.data());
if (neon_stat == NE_TIMEOUT && (++retry) < 3)
continue;
/* delete the hook again, otherwise they get chained as they are with the session */
ne_unhook_post_headers( _session, DownloadContext::install_content_reader, &writeCtx );
// ne_set_notifier(_session, 0, 0);
/* possible return codes are:
* NE_OK, NE_AUTH, NE_CONNECT, NE_TIMEOUT, NE_ERROR (from ne_request.h)
*/
if( neon_stat != NE_OK ) {
updateErrorFromSession(neon_stat);
qDebug("Error GET: Neon: %d", neon_stat);
return CSYNC_INSTRUCTION_ERROR;
} else {
const ne_status *status = ne_get_status( req.data() );
qDebug("GET http result %d (%s)", status->code, status->reason_phrase ? status->reason_phrase : "<empty");
if( status->klass != 2 ) {
qDebug("sendfile request failed with http status %d!", status->code);
errorCode = status->code;
errorString = QString::fromUtf8(status->reason_phrase);
return CSYNC_INSTRUCTION_ERROR;
}
}
break;
} while (1);
tmpFile.close();
tmpFile.flush();
if (!tmpFile.fileEngine()->rename(_localDir + item._file)) {
errorString = tmpFile.errorString();
return CSYNC_INSTRUCTION_ERROR;
}
tmpFile.setAutoRemove(false);
return CSYNC_INSTRUCTION_UPDATED;
}
csync_instructions_e OwncloudPropagator::remoteRename(const SyncFileItem& )
{
qFatal("unimplemented");
return CSYNC_INSTRUCTION_ERROR;
}
void OwncloudPropagator::updateErrorFromSession(int neon_code)
{
qFatal("unimplemented");
//don't forget to update errorCode to the http code
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef OWNCLOUDPROPAGATOR_H
#define OWNCLOUDPROPAGATOR_H
#include "syncfileitem.h"
struct ne_session_s;
struct ne_decompress_s;
namespace Mirall {
class OwncloudPropagator {
QString _localDir; // absolute path to the local directory. ends with '/'
QString _remoteDir; // path to the root of the remote. ends with '/'
ne_session_s *_session;
csync_instructions_e localRemove(const SyncFileItem &);
csync_instructions_e localMkdir(const SyncFileItem &);
csync_instructions_e remoteRemove(const SyncFileItem &);
csync_instructions_e remoteMkdir(const SyncFileItem &);
csync_instructions_e downloadFile(const SyncFileItem &);
csync_instructions_e uploadFile(const SyncFileItem &);
csync_instructions_e remoteRename(const SyncFileItem &);
/* fetch the error code and string from the session */
void updateErrorFromSession(int neon_code = 0);
public:
OwncloudPropagator(ne_session_s *session, const QString &localDir, const QString &remoteDir)
: _session(session)
, errorCode(0)
, _localDir(localDir)
, _remoteDir(remoteDir) {
if (!localDir.endsWith(QChar('/'))) _localDir+='/';
if (!remoteDir.endsWith(QChar('/'))) _remoteDir+='/';
}
csync_instructions_e propagate(const SyncFileItem &);
QString errorString;
int errorCode;
QByteArray etag;
};
}
#endif

View file

@ -2,6 +2,7 @@
#define SYNCFILEITEM_H
#include <QVector>
#include <QString>
#include <csync.h>
@ -17,8 +18,12 @@ public:
SyncFileItem() {}
bool operator==(const SyncFileItem& item) const {
return item._file == this->_file;
friend bool operator==(const SyncFileItem& item1, const SyncFileItem& item2) {
return item1._file == item2._file;
}
friend bool operator<(const SyncFileItem& item1, const SyncFileItem& item2) {
return item1._file < item2._file;
}
bool isEmpty() const {
@ -30,8 +35,12 @@ public:
QString _renameTarget;
csync_instructions_e _instruction;
Direction _dir;
bool _isDirectory;
time_t _modtime;
};
typedef QVector<SyncFileItem> SyncFileItemVector;
}