Merge branch 'propagator-ng': Fixes to work with new LGPL ocsync.

Conflicts:
	src/mirall/csyncthread.cpp
This commit is contained in:
Klaas Freitag 2013-09-04 16:33:06 +02:00
Родитель b676ffe208 3c2bb1e2bc
Коммит 8fe102662d
16 изменённых файлов: 1555 добавлений и 321 удалений

Просмотреть файл

@ -83,6 +83,7 @@ endif()
find_package(Sphinx)
find_package(PdfLatex)
find_package(QtKeychain)
find_package(Neon)
set(WITH_QTKEYCHAIN ${QTKEYCHAIN_FOUND})
set(USE_INOTIFY ${INOTIFY_FOUND})

Просмотреть файл

@ -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 )

Просмотреть файл

@ -1,153 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OwncloudDocumentation.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OwncloudDocumentation.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/OwncloudDocumentation"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OwncloudDocumentation"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

Просмотреть файл

@ -29,7 +29,7 @@ set(3rdparty_HEADER
)
qt_wrap_cpp(3rdparty_MOC ${3rdparty_HEADER})
if(NOT WIN32)
if(NOT WIN32)
list(APPEND 3rdparty_SRC 3rdparty/qtlockedfile/qtlockedfile_unix.cpp)
else()
list(APPEND 3rdparty_SRC 3rdparty/qtlockedfile/qtlockedfile_win.cpp )
@ -50,6 +50,8 @@ set(libsync_SRCS
mirall/networklocation.cpp
mirall/mirallconfigfile.cpp
mirall/csyncthread.cpp
mirall/owncloudpropagator.cpp
mirall/progressdatabase.cpp
mirall/fileutils.cpp
mirall/theme.cpp
mirall/owncloudtheme.cpp
@ -78,6 +80,7 @@ set(libsync_HEADERS
mirall/folder.h
mirall/folderwatcher.h
mirall/csyncthread.h
mirall/owncloudpropagator.h
mirall/theme.h
mirall/owncloudtheme.h
mirall/owncloudinfo.h
@ -106,7 +109,7 @@ IF( INOTIFY_FOUND )
set(libsync_HEADERS ${libsync_HEADERS} mirall/inotify.h)
set(libsync_HEADERS ${libsync_HEADERS} mirall/folderwatcher_inotify.h)
ENDIF()
IF( WIN32 )
IF( WIN32 )
set(libsync_SRCS ${libsync_SRCS} mirall/folderwatcher_win.cpp)
set(libsync_HEADERS ${libsync_HEADERS} mirall/folderwatcher_win.h)
ENDIF()
@ -117,10 +120,25 @@ ENDIF()
qt_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)
@ -232,7 +250,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})
qt_wrap_cpp(mirallMoc ${mirall_HEADERS})
@ -312,6 +330,7 @@ set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
target_link_libraries( ${APPLICATION_EXECUTABLE} ${QT_LIBRARIES} )
target_link_libraries( ${APPLICATION_EXECUTABLE} ${synclib_NAME} )
target_link_libraries( ${APPLICATION_EXECUTABLE} ${CSYNC_LIBRARY} )
target_link_libraries( ${APPLICATION_EXECUTABLE} ${NEON_LIBRARIES} )
install(TARGETS ${APPLICATION_EXECUTABLE}
RUNTIME DESTINATION bin
@ -337,3 +356,15 @@ if(KRAZY2_EXECUTABLE)
)
endif()
set(OWNCLOUDCMD_SRC owncloudcmd/owncloudcmd.cpp)
add_executable(owncloudcmd ${OWNCLOUDCMD_SRC})
set_target_properties( owncloudcmd PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} )
target_link_libraries(owncloudcmd owncloudsync)
target_link_libraries(owncloudcmd ${CSYNC_LIBRARY})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/mirall)
install(TARGETS owncloudcmd
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

Просмотреть файл

@ -18,6 +18,8 @@
#include "mirall/theme.h"
#include "mirall/logger.h"
#include "mirall/owncloudinfo.h"
#include "owncloudpropagator.h"
#include "progressdatabase.h"
#include "creds/abstractcredentials.h"
#ifdef Q_OS_WIN
@ -47,11 +49,15 @@ 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();
qRegisterMetaType<SyncFileItem>("SyncFileItem");
qRegisterMetaType<CSYNC_STATUS>("CSYNC_STATUS");
}
CSyncThread::~CSyncThread()
@ -61,106 +67,106 @@ CSyncThread::~CSyncThread()
//Convert an error code from csync to a user readable string.
// Keep that function thread safe as it can be called from the sync thread or the main thread
QString CSyncThread::csyncErrorToString( CSYNC_ERROR_CODE err, const char *errString )
QString CSyncThread::csyncErrorToString(CSYNC_STATUS err)
{
QString errStr;
switch( err ) {
case CSYNC_ERR_NONE:
case CSYNC_STATUS_OK:
errStr = tr("Success.");
break;
case CSYNC_ERR_LOG:
errStr = tr("CSync Logging setup failed.");
break;
case CSYNC_ERR_LOCK:
case CSYNC_STATUS_NO_LOCK:
errStr = tr("CSync failed to create a lock file.");
break;
case CSYNC_ERR_STATEDB_LOAD:
case CSYNC_STATUS_STATEDB_LOAD_ERROR:
errStr = tr("CSync failed to load the state db.");
break;
case CSYNC_ERR_MODULE:
case CSYNC_STATUS_STATEDB_WRITE_ERROR:
errStr = tr("CSync failed to write the state db.");
break;
case CSYNC_STATUS_NO_MODULE:
errStr = tr("<p>The %1 plugin for csync could not be loaded.<br/>Please verify the installation!</p>").arg(Theme::instance()->appNameGUI());
break;
case CSYNC_ERR_TIMESKEW:
case CSYNC_STATUS_TIMESKEW:
errStr = tr("The system time on this client is different than the system time on the server. "
"Please use a time synchronization service (NTP) on the server and client machines "
"so that the times remain the same.");
break;
case CSYNC_ERR_FILESYSTEM:
case CSYNC_STATUS_FILESYSTEM_UNKNOWN:
errStr = tr("CSync could not detect the filesystem type.");
break;
case CSYNC_ERR_TREE:
case CSYNC_STATUS_TREE_ERROR:
errStr = tr("CSync got an error while processing internal trees.");
break;
case CSYNC_ERR_MEM:
case CSYNC_STATUS_MEMORY_ERROR:
errStr = tr("CSync failed to reserve memory.");
break;
case CSYNC_ERR_PARAM:
case CSYNC_STATUS_PARAM_ERROR:
errStr = tr("CSync fatal parameter error.");
break;
case CSYNC_ERR_UPDATE:
case CSYNC_STATUS_UPDATE_ERROR:
errStr = tr("CSync processing step update failed.");
break;
case CSYNC_ERR_RECONCILE:
case CSYNC_STATUS_RECONCILE_ERROR:
errStr = tr("CSync processing step reconcile failed.");
break;
case CSYNC_ERR_PROPAGATE:
case CSYNC_STATUS_PROPAGATE_ERROR:
errStr = tr("CSync processing step propagate failed.");
break;
case CSYNC_ERR_ACCESS_FAILED:
case CSYNC_STATUS_REMOTE_ACCESS_ERROR:
errStr = tr("<p>The target directory does not exist.</p><p>Please check the sync setup.</p>");
break;
case CSYNC_ERR_REMOTE_CREATE:
case CSYNC_ERR_REMOTE_STAT:
case CSYNC_STATUS_REMOTE_CREATE_ERROR:
case CSYNC_STATUS_REMOTE_STAT_ERROR:
errStr = tr("A remote file can not be written. Please check the remote access.");
break;
case CSYNC_ERR_LOCAL_CREATE:
case CSYNC_ERR_LOCAL_STAT:
case CSYNC_STATUS_LOCAL_CREATE_ERROR:
case CSYNC_STATUS_LOCAL_STAT_ERROR:
errStr = tr("The local filesystem can not be written. Please check permissions.");
break;
case CSYNC_ERR_PROXY:
case CSYNC_STATUS_PROXY_ERROR:
errStr = tr("CSync failed to connect through a proxy.");
break;
case CSYNC_ERR_LOOKUP:
case CSYNC_STATUS_PROXY_AUTH_ERROR:
errStr = tr("CSync could not authenticate at the proxy.");
break;
case CSYNC_STATUS_LOOKUP_ERROR:
errStr = tr("CSync failed to lookup proxy or server.");
break;
case CSYNC_ERR_AUTH_SERVER:
case CSYNC_STATUS_SERVER_AUTH_ERROR:
errStr = tr("CSync failed to authenticate at the %1 server.").arg(Theme::instance()->appNameGUI());
break;
case CSYNC_ERR_AUTH_PROXY:
errStr = tr("CSync failed to authenticate at the proxy.");
break;
case CSYNC_ERR_CONNECT:
case CSYNC_STATUS_CONNECT_ERROR:
errStr = tr("CSync failed to connect to the network.");
break;
case CSYNC_ERR_TIMEOUT:
case CSYNC_STATUS_TIMEOUT:
errStr = tr("A network connection timeout happend.");
break;
case CSYNC_ERR_HTTP:
case CSYNC_STATUS_HTTP_ERROR:
errStr = tr("A HTTP transmission error happened.");
break;
case CSYNC_ERR_PERM:
errStr = tr("CSync: Permission deniend.");
case CSYNC_STATUS_PERMISSION_DENIED:
errStr = tr("CSync failed due to not handled permission deniend.");
break;
case CSYNC_ERR_NOT_FOUND:
errStr = tr("CSync: File not found.");
case CSYNC_STATUS_NOT_FOUND:
errStr = tr("CSync failed to find a specific file.");
break;
case CSYNC_ERR_EXISTS:
errStr = tr("CSync: Directory already exists.");
case CSYNC_STATUS_FILE_EXISTS:
errStr = tr("CSync tried to create a directory that already exists.");
break;
case CSYNC_ERR_NOSPC:
errStr = tr("CSync: No space left on %1 server.").arg(Theme::instance()->appNameGUI());
case CSYNC_STATUS_OUT_OF_SPACE:
errStr = tr("CSync: No space on %1 server available.").arg(Theme::instance()->appNameGUI());
break;
case CSYNC_ERR_UNSPEC:
errStr = tr("CSync: unspecified error.");
case CSYNC_STATUS_QUOTA_EXCEEDED:
errStr = tr("CSync: No space on %1 server available.").arg(Theme::instance()->appNameGUI());
break;
case CSYNC_STATUS_UNSUCCESSFUL:
errStr = tr("CSync unspecified error.");
default:
errStr = tr("An internal error number %1 happend.").arg( (int) err );
}
if( errString ) {
errStr += tr("<br/>Backend Message: ")+QString::fromUtf8(errString);
}
return errStr;
}
@ -177,7 +183,7 @@ int CSyncThread::treewalkRemote( TREE_WALK_FILE* file, void *data )
int CSyncThread::walkFinalize(TREE_WALK_FILE* file, void *data )
{
return static_cast<CSyncThread*>(data)->treewalkError( file);
return static_cast<CSyncThread*>(data)->treewalkFinalize( file);
}
int CSyncThread::treewalkFile( TREE_WALK_FILE *file, bool remote )
@ -185,11 +191,19 @@ int CSyncThread::treewalkFile( TREE_WALK_FILE *file, bool remote )
if( ! file ) return -1;
SyncFileItem item;
item._file = QString::fromUtf8( file->path );
item._originalFile = file->path;
item._instruction = file->instruction;
item._dir = SyncFileItem::None;
if(file->error_string) {
item._errorString = QString::fromUtf8(file->error_string);
}
item._isDirectory = file->type == CSYNC_FTW_TYPE_DIR;
item._modtime = file->modtime;
item._etag = file->md5;
// item._size = file->size;
SyncFileItem::Direction dir;
int re = 0;
@ -215,6 +229,8 @@ int CSyncThread::treewalkFile( TREE_WALK_FILE *file, bool remote )
case CSYNC_INSTRUCTION_RENAME:
dir = !remote ? SyncFileItem::Down : SyncFileItem::Up;
item._renameTarget = QString::fromUtf8( file->rename_path );
if (item._isDirectory)
_renamedFolders.insert(item._file, item._renameTarget);
break;
case CSYNC_INSTRUCTION_REMOVE:
dir = !remote ? SyncFileItem::Down : SyncFileItem::Up;
@ -250,63 +266,49 @@ int CSyncThread::treewalkFile( TREE_WALK_FILE *file, bool remote )
}
item._dir = dir;
_mutex.lock();
_syncedItems.append(item);
_mutex.unlock();
return re;
}
int CSyncThread::treewalkError(TREE_WALK_FILE* file)
int CSyncThread::treewalkFinalize(TREE_WALK_FILE* file)
{
SyncFileItem item; // only used for search.
item._file= QString::fromUtf8(file->path);
int indx = _syncedItems.indexOf(item);
if ( indx == -1 )
if (file->instruction == CSYNC_INSTRUCTION_IGNORE)
return 0;
if( file &&
(file->instruction == CSYNC_INSTRUCTION_STAT_ERROR ||
file->instruction == CSYNC_INSTRUCTION_ERROR) ) {
_mutex.lock();
_syncedItems[indx]._instruction = file->instruction;
_syncedItems[indx]._errorString = QString::fromUtf8(file->error_string);
_mutex.unlock();
}
// Update the instruction and etag in the csync rb_tree so it is saved on the database
QHash<QByteArray, Action>::const_iterator action = _performedActions.constFind(file->path);
if (action != _performedActions.constEnd()) {
if (file->instruction != CSYNC_INSTRUCTION_NONE) {
// it is NONE if we are in the wrong tree (remote vs. local)
qDebug() << "UPDATING " << file->path << action->instruction;
file->instruction = action->instruction;
}
if (!action->etag.isNull()) {
// Update the etag even for INSTRUCTION_NONE (eg. renames)
file->md5 = action->etag.constData();
}
}
return 0;
}
struct CSyncRunScopeHelper {
CSyncRunScopeHelper(CSYNC *ctx, CSyncThread *parent)
: _ctx(ctx), _parent(parent)
{
_t.start();
}
~CSyncRunScopeHelper() {
csync_commit(_ctx);
qDebug() << "CSync run took " << _t.elapsed() << " Milliseconds";
emit(_parent->finished());
_parent->_syncMutex.unlock();
}
CSYNC *_ctx;
QTime _t;
CSyncThread *_parent;
};
void CSyncThread::handleSyncError(CSYNC *ctx, const char *state) {
CSYNC_ERROR_CODE err = csync_get_error( ctx );
const char *errMsg = csync_get_error_string( ctx );
QString errStr = csyncErrorToString(err, errMsg);
CSYNC_STATUS err = CSYNC_STATUS(csync_get_status( ctx ));
const char *errMsg = csync_get_status_string( ctx );
QString errStr = csyncErrorToString(err);
if( errMsg ) {
errStr += QLatin1String("<br/>");
errStr += QString::fromUtf8(errMsg);
}
qDebug() << " #### ERROR during "<< state << ": " << errStr;
switch (err) {
case CSYNC_ERR_SERVICE_UNAVAILABLE:
case CSYNC_ERR_CONNECT:
if( CSYNC_STATUS_IS_EQUAL( err, CSYNC_STATUS_SERVICE_UNAVAILABLE ) ||
CSYNC_STATUS_IS_EQUAL( err, CSYNC_STATUS_CONNECT_ERROR )) {
emit csyncUnavailable();
break;
default:
} else {
emit csyncError(errStr);
}
}
@ -324,34 +326,16 @@ void CSyncThread::startSync()
qDebug() << Q_FUNC_INFO << "Sync started";
qDebug() << "starting to sync " << qApp->thread() << QThread::currentThread();
_syncedItems.clear();
_mutex.lock();
_syncedItems.clear();
_needsUpdate = false;
_mutex.unlock();
// cleans up behind us and emits finished() to ease error handling
CSyncRunScopeHelper helper(_csync_ctx, this);
// maybe move this somewhere else where it can influence a running sync?
MirallConfigFile cfg;
int downloadLimit = 0;
if (cfg.useDownloadLimit()) {
downloadLimit = cfg.downloadLimit() * 1000;
}
csync_set_module_property(_csync_ctx, "bandwidth_limit_download", &downloadLimit);
int uploadLimit = -75; // 75%
int useUpLimit = cfg.useUploadLimit();
if ( useUpLimit >= 1) {
uploadLimit = cfg.uploadLimit() * 1000;
} else if (useUpLimit == 0) {
uploadLimit = 0;
}
csync_set_module_property(_csync_ctx, "bandwidth_limit_upload", &uploadLimit);
csync_set_progress_callback( _csync_ctx, cb_progress );
csync_set_module_property(_csync_ctx, "csync_context", _csync_ctx);
csync_set_userdata(_csync_ctx, this);
@ -378,18 +362,24 @@ void CSyncThread::startSync()
_syncTime.start();
QElapsedTimer updateTime;
updateTime.start();
qDebug() << "#### Update start #################################################### >>";
if( csync_update(_csync_ctx) < 0 ) {
handleSyncError(_csync_ctx, "csync_update");
return;
}
qDebug() << "<<#### Update end ###########################################################";
qDebug() << "<<#### Update end #################################################### " << updateTime.elapsed();
if( csync_reconcile(_csync_ctx) < 0 ) {
handleSyncError(_csync_ctx, "csync_reconcile");
return;
}
_progressInfo = Progress::Info();
_hasFiles = false;
bool walkOk = true;
if( csync_walk_local_tree(_csync_ctx, &treewalkLocal, 0) < 0 ) {
@ -400,9 +390,17 @@ void CSyncThread::startSync()
qDebug() << "Error in remote treewalk.";
}
// Adjust the paths for the renames.
for (SyncFileItemVector::iterator it = _syncedItems.begin();
it != _syncedItems.end(); ++it) {
it->_file = adjustRenamedPath(it->_file);
}
qSort(_syncedItems);
if (!_hasFiles && !_syncedItems.isEmpty()) {
qDebug() << Q_FUNC_INFO << "All the files are going to be removed, asking the user";
bool cancel = true;
bool cancel = false;
emit aboutToRemoveAllFiles(_syncedItems.first()._dir, &cancel);
if (cancel) {
qDebug() << Q_FUNC_INFO << "Abort sync";
@ -413,21 +411,135 @@ void CSyncThread::startSync()
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);
if( walkOk ) {
if( csync_walk_local_tree(_csync_ctx, &walkFinalize, 0) < 0 ||
csync_walk_remote_tree(_csync_ctx, &walkFinalize, 0 ) < 0 ) {
qDebug() << "Error in finalize treewalk.";
_progressDataBase.load(_localPath);
_propagator.reset(new OwncloudPropagator (session, _localPath, _remotePath, &_progressDataBase));
connect(_propagator.data(), SIGNAL(completed(SyncFileItem, CSYNC_STATUS)),
this, SLOT(transferCompleted(SyncFileItem, CSYNC_STATUS)), Qt::QueuedConnection);
connect(_propagator.data(), SIGNAL(progress(Progress::Kind,QString,quint64,quint64)),
this, SLOT(slotProgress(Progress::Kind,QString,quint64,quint64)));
_iterator = 0;
int downloadLimit = 0;
if (cfg.useDownloadLimit()) {
downloadLimit = cfg.downloadLimit() * 1000;
}
_propagator->_downloadLimit = downloadLimit;
int uploadLimit = -75; // 75%
int useUpLimit = cfg.useUploadLimit();
if ( useUpLimit >= 1) {
uploadLimit = cfg.uploadLimit() * 1000;
} else if (useUpLimit == 0) {
uploadLimit = 0;
}
_propagator->_uploadLimit = uploadLimit;
slotProgress(Progress::StartSync, QString(), 0, 0);
startNextTransfer();
}
void CSyncThread::transferCompleted(const SyncFileItem &item, CSYNC_STATUS error)
{
Action a;
a.instruction = item._instruction;
// if the propagator had an error for a file, put the error string into the synced item
if( error != CSYNC_STATUS_OK
|| a.instruction == CSYNC_INSTRUCTION_ERROR) {
// Search for the item in the starting from _iterator because it should be a bit before it.
// This works because SyncFileItem::operator== only compare the file name;
int idx = _syncedItems.lastIndexOf(item, _iterator);
if (idx >= 0) {
_syncedItems[idx]._instruction = CSYNC_INSTRUCTION_ERROR;
_syncedItems[idx]._errorString = csyncErrorToString( error );
_syncedItems[idx]._errorDetail = item._errorDetail;
_syncedItems[idx]._httpCode = item._httpCode;
qDebug() << "File " << item._file << " propagator error " << _syncedItems[idx]._errorString
<< "(" << item._errorString << ")";
}
if (item._isDirectory && item._instruction == CSYNC_INSTRUCTION_REMOVE
&& a.instruction == CSYNC_INSTRUCTION_DELETED) {
_lastDeleted = item._file;
} else {
// emit the treewalk results.
emit treeWalkResult(_syncedItems);
_lastDeleted.clear();
}
}
qDebug() << Q_FUNC_INFO << "Sync finished";
a.etag = item._etag;
_performedActions.insert(item._originalFile, a);
if (item._instruction == CSYNC_INSTRUCTION_RENAME) {
if (a.instruction == CSYNC_INSTRUCTION_DELETED) {
// we should update the etag on the destination as well
a.instruction = CSYNC_INSTRUCTION_NONE;
} else { // ERROR
a.instruction = CSYNC_INSTRUCTION_ERROR;
}
_performedActions.insert(item._renameTarget.toUtf8(), a);
}
if (!item._isDirectory && a.instruction == CSYNC_INSTRUCTION_UPDATED) {
slotProgress((item._dir != SyncFileItem::Up) ? Progress::EndDownload : Progress::EndUpload,
item._file, item._size, item._size);
_progressInfo.current_file_no++;
_progressInfo.overall_current_bytes += item._size;
}
startNextTransfer();
}
void CSyncThread::startNextTransfer()
{
while (_iterator < _syncedItems.size() && !_propagator->_hasFatalError) {
const SyncFileItem &item = _syncedItems.at(_iterator);
++_iterator;
if (!_lastDeleted.isEmpty() && item._file.startsWith(_lastDeleted)
&& item._instruction == CSYNC_INSTRUCTION_REMOVE) {
// If the item's name starts with the name of the previously deleted directory, we
// can assume this file was already destroyed by the previous recursive call.
Action a;
a.instruction = CSYNC_INSTRUCTION_DELETED;
_performedActions.insert(item._originalFile, a);
continue;
}
_propagator->_etag.clear(); // FIXME : set to the right one
if (item._instruction == CSYNC_INSTRUCTION_SYNC || item._instruction == CSYNC_INSTRUCTION_NEW
|| item._instruction == CSYNC_INSTRUCTION_CONFLICT) {
slotProgress((item._dir != SyncFileItem::Up) ? Progress::StartDownload : Progress::StartUpload,
item._file, 0, item._size);
}
_propagator->propagate(item);
return; //propagate is async.
}
// Everything is finished.
_progressDataBase.save(_localPath);
if( csync_walk_local_tree(_csync_ctx, &walkFinalize, 0) < 0 ||
csync_walk_remote_tree( _csync_ctx, &walkFinalize, 0 ) < 0 ) {
qDebug() << "Error in finalize treewalk.";
} else {
// emit the treewalk results.
emit treeWalkResult(_syncedItems);
}
csync_commit(_csync_ctx);
qDebug() << "CSync run took " << _syncTime.elapsed() << " Milliseconds";
slotProgress(Progress::EndSync,QString(), 0 , 0);
emit finished();
_propagator.reset(0);
_syncMutex.unlock();
}
Progress::Kind CSyncThread::csyncToProgressKind( enum csync_notify_type_e kind )
@ -475,33 +587,32 @@ Progress::Kind CSyncThread::csyncToProgressKind( enum csync_notify_type_e kind )
return pKind;
}
void CSyncThread::cb_progress( CSYNC_PROGRESS *progress, void *userdata )
void CSyncThread::slotProgress(Progress::Kind kind, const QString &file, quint64 curr, quint64 total)
{
if( !progress ) {
qDebug() << "No progress block in progress callback found!";
return;
}
if( !userdata ) {
qDebug() << "No thread given in progress callback!";
return;
}
Progress::Info pInfo;
CSyncThread *thread = static_cast<CSyncThread*>(userdata);
Progress::Info pInfo = _progressInfo;
pInfo.kind = thread->csyncToProgressKind( progress->kind );
pInfo.current_file = QUrl::fromEncoded( progress->path ).toString();
pInfo.file_size = progress->file_size;
pInfo.current_file_bytes = progress->curr_bytes;
pInfo.kind = kind;
pInfo.current_file = file;
pInfo.file_size = total;
pInfo.current_file_bytes = curr;
pInfo.overall_file_count = progress->overall_file_count;
pInfo.current_file_no = progress->current_file_no;
pInfo.overall_transmission_size = progress->overall_transmission_size;
pInfo.overall_current_bytes = progress->current_overall_bytes;
pInfo.overall_current_bytes += curr;
pInfo.timestamp = QDateTime::currentDateTime();
// Connect to something in folder!
thread->transmissionProgress( pInfo );
transmissionProgress( pInfo );
}
/* Given a path on the remote, give the path as it is when the rename is done */
QString CSyncThread::adjustRenamedPath(const QString& original)
{
int slashPos = original.size();
while ((slashPos = original.lastIndexOf('/' , slashPos - 1)) > 0) {
QHash< QString, QString >::const_iterator it = _renamedFolders.constFind(original.left(slashPos));
if (it != _renamedFolders.constEnd()) {
return *it + original.mid(slashPos);
}
}
return original;
}
} // ns Mirall

Просмотреть файл

@ -21,32 +21,45 @@
#include <QMutex>
#include <QThread>
#include <QString>
#include <qelapsedtimer.h>
#include <QNetworkProxy>
#include <QNetworkCookie>
#include <csync.h>
#include "mirall/syncfileitem.h"
#include "progressdatabase.h"
#include "mirall/progressdispatcher.h"
class QProcess;
Q_DECLARE_METATYPE(CSYNC_STATUS)
namespace Mirall {
class OwncloudPropagator;
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<QByteArray, Action> _performedActions;
public:
CSyncThread(CSYNC *);
CSyncThread(CSYNC *, const QString &localPath, const QString &remotePath);
~CSyncThread();
static QString csyncErrorToString( CSYNC_ERROR_CODE, const char * );
static QString csyncErrorToString( CSYNC_STATUS);
Q_INVOKABLE void startSync();
signals:
void fileReceived( const QString& );
void fileRemoved( const QString& );
void csyncError( const QString& );
void csyncWarning( const QString& );
void csyncUnavailable();
@ -61,29 +74,48 @@ signals:
void aboutToRemoveAllFiles(SyncFileItem::Direction direction, bool *cancel);
private slots:
void transferCompleted(const SyncFileItem& item, CSYNC_STATUS error);
void startNextTransfer();
void slotProgress(Progress::Kind kind, const QString& file, quint64, quint64);
private:
void handleSyncError(CSYNC *ctx, const char *state);
static void cb_progress( CSYNC_PROGRESS *progress, void *userdata );
static int treewalkLocal( TREE_WALK_FILE*, void *);
static int treewalkRemote( TREE_WALK_FILE*, void *);
int treewalkFile( TREE_WALK_FILE*, bool );
int treewalkError( TREE_WALK_FILE* );
int treewalkFinalize( TREE_WALK_FILE* );
Progress::Kind csyncToProgressKind( enum csync_notify_type_e kind );
static int walkFinalize(TREE_WALK_FILE*, void* );
static QMutex _mutex;
static QMutex _syncMutex;
SyncFileItemVector _syncedItems;
int _iterator; // index in _syncedItems for the next item to process.
ProgressDatabase _progressDataBase;
CSYNC *_csync_ctx;
bool _needsUpdate;
QString _localPath;
QString _remotePath;
QScopedPointer <OwncloudPropagator> _propagator;
QElapsedTimer _syncTime;
QString _lastDeleted; // if the last item was a path and it has been deleted
// maps the origin and the target of the folders that have been renamed
QHash<QString, QString> _renamedFolders;
QString adjustRenamedPath(const QString &original);
bool _hasFiles; // true if there is at least one file that is not ignored or removed
Progress::Info _progressInfo;
int _downloadLimit;
int _uploadLimit;
friend struct CSyncRunScopeHelper;
};

Просмотреть файл

@ -35,8 +35,7 @@
namespace Mirall {
void csyncLogCatcher(CSYNC */*ctx*/,
int /*verbosity*/,
void csyncLogCatcher(int /*verbosity*/,
const char */*function*/,
const char *buffer,
void */*userdata*/)
@ -92,8 +91,8 @@ bool Folder::init()
slotCSyncError(tr("Unable to create csync-context"));
_csync_ctx = 0;
} else {
csync_set_log_callback( _csync_ctx, csyncLogCatcher );
csync_set_log_verbosity(_csync_ctx, 11);
csync_set_log_callback( csyncLogCatcher );
csync_set_log_level( 11 );
MirallConfigFile cfgFile;
csync_set_config_dir( _csync_ctx, cfgFile.configPath().toUtf8() );
@ -103,8 +102,14 @@ bool Folder::init()
cfgFile.getCredentials()->syncContextPreInit(_csync_ctx);
if( csync_init( _csync_ctx ) < 0 ) {
qDebug() << "Could not initialize csync!" << csync_get_error(_csync_ctx) << csync_get_error_string(_csync_ctx);
slotCSyncError(CSyncThread::csyncErrorToString(csync_get_error(_csync_ctx), csync_get_error_string(_csync_ctx)));
qDebug() << "Could not initialize csync!" << csync_get_status(_csync_ctx) << csync_get_status_string(_csync_ctx);
QString errStr = CSyncThread::csyncErrorToString(CSYNC_STATUS(csync_get_status(_csync_ctx)));
const char *errMsg = csync_get_status_string(_csync_ctx);
if( errMsg ) {
errStr += QLatin1String("<br/>");
errStr += QString::fromUtf8(errMsg);
}
slotCSyncError(errStr);
csync_destroy(_csync_ctx);
_csync_ctx = 0;
}
@ -563,7 +568,7 @@ void Folder::startSync(const QStringList &pathList)
_thread = new QThread(this);
_thread->setPriority(QThread::LowPriority);
setIgnoredFiles();
_csync = new CSyncThread( _csync_ctx );
_csync = new CSyncThread( _csync_ctx, path(), QUrl(ownCloudInfo::instance()->webdavUrl() + secondPath()).path());
_csync->moveToThread(_thread);

Просмотреть файл

@ -0,0 +1,700 @@
/*
* 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 "progressdatabase.h"
#include <httpbf.h>
#include <qfile.h>
#include <qdir.h>
#include <qdiriterator.h>
#include <qtemporaryfile.h>
#include <qabstractfileengine.h>
#include <qdebug.h>
#include <QDateTime>
#include <neon/ne_basic.h>
#include <neon/ne_socket.h>
#include <neon/ne_session.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>
// We use some internals of csync:
extern "C" int c_utimes(const char *, const struct timeval *);
extern "C" void csync_win32_set_file_hidden( const char *file, bool 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); }
};
void OwncloudPropagator::propagate(const SyncFileItem &item)
{
_errorCode = CSYNC_STATUS_OK;
_errorString.clear();
_httpStatusCode = 0;
switch(item._instruction) {
case CSYNC_INSTRUCTION_REMOVE:
_instruction = item._dir == SyncFileItem::Down ? localRemove(item) : remoteRemove(item);
break;
case CSYNC_INSTRUCTION_NEW:
if (item._isDirectory) {
_instruction = item._dir == SyncFileItem::Down ? localMkdir(item) : remoteMkdir(item);
break;
} //fall trough
case CSYNC_INSTRUCTION_SYNC:
if (item._isDirectory) {
// Should we set the mtime?
_instruction = CSYNC_INSTRUCTION_UPDATED;
break;
}
_instruction = item._dir == SyncFileItem::Down ? downloadFile(item) : uploadFile(item);
break;
case CSYNC_INSTRUCTION_CONFLICT:
if (item._isDirectory) {
_instruction = CSYNC_INSTRUCTION_UPDATED;
break;
}
_instruction = downloadFile(item, true);
break;
case CSYNC_INSTRUCTION_RENAME:
_instruction = remoteRename(item);
break;
default:
_instruction = item._instruction;
break;
}
SyncFileItem newItem = item;
newItem._instruction = _instruction;
newItem._errorDetail = _errorString;
newItem._httpCode = _httpStatusCode;
newItem._etag = _etag;
emit completed(newItem, _errorCode);
}
// compare two files with given filename and return true if they have the same content
static bool fileEquals(const QString &fn1, const QString &fn2) {
QFile f1(fn1);
QFile f2(fn2);
if (!f1.open(QIODevice::ReadOnly) || !f2.open(QIODevice::ReadOnly)) {
qDebug() << "fileEquals: Failed to open " << fn1 << "or" << fn2;
return false;
}
if (f1.size() != f2.size()) {
return false;
}
const int BufferSize = 16 * 1024;
char buffer1[BufferSize];
char buffer2[BufferSize];
do {
int r = f1.read(buffer1, BufferSize);
if (f2.read(buffer2, BufferSize) != r) {
// this should normaly not happen: the file are supposed to have the same size.
return false;
}
if (r <= 0) {
return true;
}
if (memcmp(buffer1, buffer2, r) != 0) {
return false;
}
} while (true);
return false;
}
// 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();
}
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)) {
_errorString = "could not create directory " + _localDir + item._file;
return CSYNC_INSTRUCTION_ERROR;
}
return CSYNC_INSTRUCTION_UPDATED;
}
csync_instructions_e OwncloudPropagator::remoteRemove(const SyncFileItem &item)
{
bool error = false;
QScopedPointer<char, QScopedPointerPodDeleter> uri(ne_path_escape((_remoteDir + item._file).toUtf8()));
int rc = ne_delete(_session, uri.data());
error = updateErrorFromSession(rc);
if (error) {
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()));
bool error = false;
int rc = ne_mkcol(_session, uri.data());
error = updateErrorFromSession( rc );
if( error ) {
/* Special for mkcol: it returns 405 if the directory already exists.
* Ignre that error */
if (_httpStatusCode != 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()));
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());
if (const ProgressDatabase::UploadInfo* progressInfo = _progressDb->getUploadInfo(item._file)) {
if (progressInfo->mtime == item._modtime) {
trans->start_id = progressInfo->chunk;
trans->transfer_id = progressInfo->transferid;
}
_progressDb->remove(item._file);
}
ne_set_notifier(_session, notify_status_cb, this);
_lastTime.restart();
_lastProgress = 0;
_chunked_done = 0;
_chunked_total_size = item._size;
_currentFile = item._file;
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(trans.data(), state);
_httpStatusCode = hbf_fail_http_code(trans.data());
if (trans->start_id > 0) {
ProgressDatabase::UploadInfo pi;
pi.chunk = trans->start_id;
pi.transferid = trans->transfer_id;
pi.mtime = item._modtime;
_progressDb->setUploadInfo(item._file, pi);
}
return CSYNC_INSTRUCTION_ERROR;
}
}
} while( !finished );
ne_set_notifier(_session, 0, 0);
updateMTimeAndETag(uri.data(), item._modtime);
return CSYNC_INSTRUCTION_UPDATED;
}
static QByteArray parseEtag(ne_request *req) {
const char *header = ne_get_response_header(req, "etag");
if(header && header [0] == '"' && header[ strlen(header)-1] == '"') {
return QByteArray(header + 1, strlen(header)-2);
} else {
return header;
}
}
void OwncloudPropagator::updateMTimeAndETag(const char* uri, time_t mtime)
{
QByteArray modtime = QByteArray::number(qlonglong(mtime));
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, ops );
bool error = updateErrorFromSession( rc );
if( error ) {
// FIXME: We could not set the mtime. Error or not?
qDebug() << "PROP-Patching of modified date failed.";
}
// get the etag
QScopedPointer<ne_request, ScopedPointerHelpers> req(ne_request_create(_session, "HEAD", uri));
int neon_stat = ne_request_dispatch(req.data());
if( updateErrorFromSession(neon_stat, req.data()) ) {
// error happend
qDebug() << "Could not issue HEAD request for ETag.";
} else {
_etag = parseEtag(req.data());
}
}
class DownloadContext {
public:
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 = static_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 = static_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 );
}
}
};
csync_instructions_e OwncloudPropagator::downloadFile(const SyncFileItem &item, bool isConflict)
{
QString tmpFileName;
const ProgressDatabase::DownloadInfo* progressInfo = _progressDb->getDownloadInfo(item._file);
if (progressInfo) {
if (progressInfo->etag != item._etag) {
QFile::remove(_localDir + progressInfo->tmpfile);
} else {
tmpFileName = progressInfo->tmpfile;
}
_progressDb->remove(item._file);
}
if (tmpFileName.isEmpty()) {
tmpFileName = item._file;
//add a dot at the begining of the filename to hide the file.
int slashPos = tmpFileName.lastIndexOf('/');
tmpFileName.insert(slashPos+1, '.');
//add the suffix
tmpFileName += ".~" + QString::number(uint(qrand()), 16);
}
QFile tmpFile(_localDir + tmpFileName);
if (!tmpFile.open(QIODevice::Append)) {
_errorString = tmpFile.errorString();
_errorCode = CSYNC_STATUS_LOCAL_CREATE_ERROR;
return CSYNC_INSTRUCTION_ERROR;
}
csync_win32_set_file_hidden(tmpFileName.toUtf8().constData(), true);
{
ProgressDatabase::DownloadInfo pi;
pi.etag = item._etag;
pi.tmpfile = tmpFileName;
_progressDb->setDownloadInfo(item._file, pi);
_progressDb->save(_localDir);
}
/* actually do the request */
int retry = 0;
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);
ne_set_notifier(_session, notify_status_cb, this);
_lastProgress = 0;
_lastTime.start();
_chunked_done = _chunked_total_size = 0;
_currentFile = item._file;
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);
_chunked_done = _chunked_total_size = 0;
if( updateErrorFromSession(neon_stat, req.data() ) ) {
qDebug("Error GET: Neon: %d", neon_stat);
if (tmpFile.size() == 0) {
// don't keep the temporary file if it is empty.
tmpFile.close();
tmpFile.remove();
_progressDb->remove(item._file);
}
return CSYNC_INSTRUCTION_ERROR;
}
_etag = parseEtag(req.data());
break;
} while (1);
tmpFile.close();
tmpFile.flush();
//In case of conflict, make a backup of the old file
if (isConflict) {
QString fn = _localDir + item._file;
// compare the files to see if there was an actual conflict.
if (fileEquals(fn, tmpFile.fileName())) {
tmpFile.remove();
_progressDb->remove(item._file);
return CSYNC_INSTRUCTION_UPDATED;
}
QFile f(fn);
// Add _conflict-XXXX before the extention.
int dotLocation = fn.lastIndexOf('.');
// If no extention, add it at the end (take care of cases like foo/.hidden or foo.bar/file)
if (dotLocation <= fn.lastIndexOf('/') + 1) {
dotLocation = fn.size();
}
fn.insert(dotLocation, "_conflict-" + QDateTime::fromTime_t(item._modtime).toString("yyyyMMdd-hhmmss"));
if (!f.rename(fn)) {
//If the rename fails, don't replace it.
_errorString = f.errorString();
return CSYNC_INSTRUCTION_ERROR;
}
}
csync_win32_set_file_hidden(tmpFileName.toUtf8().constData(), false);
// We want a rename that also overwite. QFile::rename does not overwite.
// Qt 5.1 has QFile::renameOverwrite we cold use. (Or even better: QSaveFile)
#ifndef QT_OS_WIN
if (!tmpFile.fileEngine()->rename(_localDir + item._file)) {
_errorString = tmpFile.errorString();
return CSYNC_INSTRUCTION_ERROR;
}
#else //QT_OS_WIN
if (::MoveFileEx((wchar_t*)tmpFile.fileName().utf16(),
(wchar_t*)QString(_localDir + item._file).utf16(),
MOVEFILE_REPLACE_EXISTING) != 0) {
wchar_t *string = 0;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
NULL, ::GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&string, 0, NULL);
_errorString = QString::fromWCharArray(string);
LocalFree((HLOCAL)string);
return CSYNC_INSTRUCTION_ERROR;
}
#endif
_progressDb->remove(item._file);
struct timeval times[2];
times[0].tv_sec = times[1].tv_sec = item._modtime;
times[0].tv_usec = times[1].tv_usec = 0;
c_utimes((_localDir + item._file).toUtf8().data(), times);
return CSYNC_INSTRUCTION_UPDATED;
}
csync_instructions_e OwncloudPropagator::remoteRename(const SyncFileItem &item)
{
if (item._file == item._renameTarget)
return CSYNC_INSTRUCTION_DELETED; // nothing to do;
QScopedPointer<char, QScopedPointerPodDeleter> uri1(ne_path_escape((_remoteDir + item._file).toUtf8()));
QScopedPointer<char, QScopedPointerPodDeleter> uri2(ne_path_escape((_remoteDir + item._renameTarget).toUtf8()));
int rc = ne_move(_session, 1, uri1.data(), uri2.data());
if (updateErrorFromSession(rc)) {
// We set the instruction to UPDATED so next try we try to rename again
return CSYNC_INSTRUCTION_UPDATED;
}
updateMTimeAndETag(uri2.data(), item._modtime);
return CSYNC_INSTRUCTION_DELETED;
}
bool OwncloudPropagator::check_neon_session()
{
bool isOk = true;
if( !_session ) {
_errorCode = CSYNC_STATUS_PARAM_ERROR;
isOk = false;
} else {
const char *p = ne_get_error( _session );
_errorString = QString::fromUtf8(p);
if( !_errorString.isEmpty() ) {
int firstSpace = _errorString.indexOf(QChar(' '));
if( firstSpace > 0 ) {
bool ok;
QString numStr = _errorString.mid(0, firstSpace);
_httpStatusCode = numStr.toInt(&ok);
if( !ok ) {
_httpStatusCode = 0;
}
}
isOk = false;
}
}
return isOk;
}
// returns true in case there was an error
bool OwncloudPropagator::updateErrorFromSession(int neon_code, ne_request *req)
{
bool re = false;
if( neon_code != NE_OK ) {
qDebug("Neon error code was %d", neon_code);
re = true; // there was an error.
}
switch(neon_code) {
case NE_OK: /* Success, but still the possiblity of problems */
if( req != NULL ) {
const ne_status *status = ne_get_status(req);
if( status ) {
if( status->klass != 2 ) {
_httpStatusCode = status->code;
_errorCode = CSYNC_STATUS_HTTP_ERROR;
_errorString = QString::fromUtf8( status->reason_phrase );
re = true;
}
} else {
re = true; // can not get the status
}
} else {
// no neon request available.
re = check_neon_session();
}
break;
case NE_ERROR: /* Generic error; use ne_get_error(session) for message */
_errorString = QString::fromUtf8( ne_get_error(_session) );
_errorCode = CSYNC_STATUS_HTTP_ERROR;
break;
case NE_LOOKUP: /* Server or proxy hostname lookup failed */
_errorString = QString::fromUtf8( ne_get_error(_session) );
_errorCode = CSYNC_STATUS_LOOKUP_ERROR;
_hasFatalError = true;
break;
case NE_AUTH: /* User authentication failed on server */
_errorString = QString::fromUtf8( ne_get_error(_session) );
_errorCode = CSYNC_STATUS_REMOTE_ACCESS_ERROR;
_hasFatalError = true;
break;
case NE_PROXYAUTH: /* User authentication failed on proxy */
_errorString = QString::fromUtf8( ne_get_error(_session) );
_errorCode = CSYNC_STATUS_PROXY_AUTH_ERROR;
_hasFatalError = true;
break;
case NE_CONNECT: /* Could not connect to server */
_errorString = QString::fromUtf8( ne_get_error(_session) );
_errorCode = CSYNC_STATUS_CONNECT_ERROR;
_hasFatalError = true;
break;
case NE_TIMEOUT: /* Connection timed out */
_errorString = QString::fromUtf8( ne_get_error(_session) );
_errorCode = CSYNC_STATUS_TIMEOUT;
_hasFatalError = true;
break;
case NE_FAILED: /* The precondition failed */
case NE_RETRY: /* Retry request (ne_end_request ONLY) */
case NE_REDIRECT: /* See ne_redirect.h */
default:
_errorString = QString::fromUtf8( ne_get_error(_session) );
_errorCode = CSYNC_STATUS_HTTP_ERROR;
break;
}
return re;
}
void OwncloudPropagator::notify_status_cb(void* userdata, ne_session_status status,
const ne_session_status_info* info)
{
OwncloudPropagator* this_ = reinterpret_cast<OwncloudPropagator *>(userdata);
if ((status == ne_status_sending || status == ne_status_recving)) {
if (info->sr.total > 0) {
emit this_->progress(Progress::Context, this_->_currentFile,
this_->_chunked_done + info->sr.progress,
this_->_chunked_total_size ? this_->_chunked_total_size : info->sr.total );
}
if (this_->_chunked_total_size && info->sr.total > 0 && info->sr.total == info->sr.progress) {
this_->_chunked_done += info->sr.total;
}
}
/* throttle connection */
int bandwidth_limit = 0;
if (status == ne_status_sending) bandwidth_limit = this_->_uploadLimit;
if (status == ne_status_recving) bandwidth_limit = this_->_downloadLimit;
if (bandwidth_limit > 0) {
int64_t diff = this_->_lastTime.nsecsElapsed() / 1000;
int64_t len = info->sr.progress - this_->_lastProgress;
if (len > 0 && diff > 0 && (1000000 * len / diff) > (int64_t)bandwidth_limit) {
int64_t wait_time = (1000000 * len / bandwidth_limit) - diff;
if (wait_time > 0) {
usleep(wait_time);
}
}
this_->_lastProgress = info->sr.progress;
this_->_lastTime.start();
} else if (bandwidth_limit < 0 && bandwidth_limit > -100) {
int64_t diff = this_->_lastTime.nsecsElapsed() / 1000;
if (diff > 0) {
// -bandwidth_limit is the % of bandwidth
int64_t wait_time = -diff * (1 + 100.0 / bandwidth_limit);
if (wait_time > 0) {
usleep(wait_time);
}
}
this_->_lastTime.start();
}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
/*
* 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 <neon/ne_request.h>
#include <QHash>
#include <QObject>
#include <qelapsedtimer.h>
#include "syncfileitem.h"
#include "progressdispatcher.h"
struct ne_session_s;
struct ne_decompress_s;
namespace Mirall {
class ProgressDatabase;
class OwncloudPropagator : public QObject {
Q_OBJECT
ne_session_s *_session;
QString _localDir; // absolute path to the local directory. ends with '/'
QString _remoteDir; // path to the root of the remote. ends with '/'
ProgressDatabase *_progressDb;
QString _errorString;
CSYNC_STATUS _errorCode;
int _httpStatusCode;
csync_instructions_e _instruction;
bool check_neon_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 &, bool isConflict = false);
csync_instructions_e uploadFile(const SyncFileItem &);
csync_instructions_e remoteRename(const SyncFileItem &);
void updateMTimeAndETag(const char *uri, time_t);
/* fetch the error code and string from the session */
bool updateErrorFromSession(int neon_code = 0, ne_request *req = NULL);
QElapsedTimer _lastTime;
quint64 _lastProgress;
quint64 _chunked_total_size;
quint64 _chunked_done;
QString _currentFile;
static void notify_status_cb (void *userdata, ne_session_status status,
const ne_session_status_info *info);
public:
OwncloudPropagator(ne_session_s *session, const QString &localDir, const QString &remoteDir,
ProgressDatabase *progressDb)
: _session(session)
, _localDir(localDir)
, _remoteDir(remoteDir)
, _progressDb(progressDb)
, _errorCode(CSYNC_STATUS_OK)
, _httpStatusCode(0)
, _hasFatalError(false)
{
if (!localDir.endsWith(QChar('/'))) _localDir+='/';
if (!remoteDir.endsWith(QChar('/'))) _remoteDir+='/';
}
void propagate(const SyncFileItem &);
QByteArray _etag;
bool _hasFatalError;
int _downloadLimit;
int _uploadLimit;
signals:
void completed(const SyncFileItem &, CSYNC_STATUS);
void progress(Progress::Kind, const QString &filename, quint64 bytes, quint64 total);
};
}
#endif

Просмотреть файл

@ -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.
*/
#include "progressdatabase.h"
#include <QFile>
namespace Mirall {
QDataStream& operator<<(QDataStream&d , const ProgressDatabase::DownloadInfo &i)
{ return d << i.tmpfile << i.etag; }
QDataStream& operator>>(QDataStream&d , ProgressDatabase::DownloadInfo &i)
{ return d >> i.tmpfile >> i.etag; }
QDataStream& operator<<(QDataStream&d , const ProgressDatabase::UploadInfo &i)
{ return d << i.chunk << i.transferid << i.size << qlonglong(i.mtime); }
QDataStream& operator>>(QDataStream&d , ProgressDatabase::UploadInfo &i) {
qlonglong mtime;
d >> i.chunk >> i.transferid >> i.size >> mtime;
i.mtime = mtime;
return d;
}
void ProgressDatabase::load(const QString& rootDirectory)
{
QMutexLocker locker(&_mutex);
QFile f(rootDirectory + "/.csync-progressdatabase");
if (!f.open(QIODevice::ReadOnly)) {
return;
}
QDataStream stream(&f);
QByteArray magic;
stream >> magic;
if (magic != "csyncpdb_1")
return;
stream >> _down >> _up;
}
void ProgressDatabase::save(const QString& rootDirectory)
{
QMutexLocker locker(&_mutex);
QFile f(rootDirectory + "/.csync-progressdatabase");
if (!f.open(QIODevice::WriteOnly)) {
return;
}
QDataStream stream(&f);
stream << QByteArray("csyncpdb_1") << _down << _up;
}
}

Просмотреть файл

@ -0,0 +1,86 @@
/*
* 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 PROGRESSDATABASE_H
#define PROGRESSDATABASE_H
#include <QString>
#include <QList>
#include <QHash>
#include <QMutex>
namespace Mirall {
class ProgressDatabase {
public:
struct DownloadInfo {
QString tmpfile;
QByteArray etag;
};
typedef QHash<QString, DownloadInfo > DownloadInfoHash;
struct UploadInfo {
int chunk;
int transferid;
quint64 size; //currently unused
time_t mtime;
};
typedef QHash<QString, UploadInfo > UploadInfoHash;
void load(const QString &rootDirectory);
void save(const QString &rootDirectory);
const DownloadInfo *getDownloadInfo(const QString &file) const {
QMutexLocker locker(&_mutex);
DownloadInfoHash::const_iterator it = _down.constFind(file);
if (it == _down.end())
return 0;
return &it.value();
}
const UploadInfo *getUploadInfo(const QString &file) const {
QMutexLocker locker(&_mutex);
UploadInfoHash::const_iterator it = _up.constFind(file);
if (it == _up.end())
return 0;
return &it.value();
}
void setDownloadInfo(const QString &file, const DownloadInfo &i) {
QMutexLocker locker(&_mutex);
_down[file] = i;
}
void setUploadInfo(const QString &file, const UploadInfo &i) {
QMutexLocker locker(&_mutex);
_up[file] = i;
}
void remove(const QString &file) {
QMutexLocker locker(&_mutex);
_down.remove(file);
_up.remove(file);
}
private:
DownloadInfoHash _down;
UploadInfoHash _up;
QString _rootPath;
mutable QMutex _mutex;
};
}
#endif

Просмотреть файл

@ -27,7 +27,7 @@ namespace Mirall {
*/
namespace Progress
{
typedef enum {
enum Kind {
Invalid,
StartSync,
Download,
@ -42,9 +42,9 @@ namespace Progress
StartDelete,
EndDelete,
Error
} Kind;
};
typedef struct {
struct Info {
Kind kind;
QString folder;
QString current_file;
@ -58,15 +58,18 @@ namespace Progress
QDateTime timestamp;
} Info;
Info() : kind(Invalid), file_size(0), current_file_bytes(0),
overall_file_count(0), current_file_no(0),
overall_transmission_size(0), overall_current_bytes(0) { }
};
typedef struct {
struct SyncProblem {
QString folder;
QString current_file;
QString error_message;
int error_code;
QDateTime timestamp;
} SyncProblem;
};
QString asActionString( Kind );
QString asResultString( Kind );

Просмотреть файл

@ -2,6 +2,8 @@
#define SYNCFILEITEM_H
#include <QVector>
#include <QString>
#include <QMetaType>
#include <csync.h>
@ -24,10 +26,27 @@ 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) {
// Delete at the end:
if (item1._instruction == CSYNC_INSTRUCTION_REMOVE && item2._instruction != CSYNC_INSTRUCTION_REMOVE)
return false;
if (item1._instruction != CSYNC_INSTRUCTION_REMOVE && item2._instruction == CSYNC_INSTRUCTION_REMOVE)
return true;
// Sort by destination
return item1.destination() < item2.destination();
}
QString destination() const {
return _instruction == CSYNC_INSTRUCTION_RENAME ? _renameTarget : _file;
}
bool isEmpty() const {
return _file.isEmpty();
}
@ -35,14 +54,27 @@ public:
// variables
QString _file;
QString _renameTarget;
QString _errorString;
QByteArray _originalFile; // as it is in the csync tree
csync_instructions_e _instruction;
Direction _dir;
bool _isDirectory;
time_t _modtime;
QByteArray _etag;
quint64 _size;
QString _errorString;
QString _errorDetail;
int _httpCode;
Type _type;
};
typedef QVector<SyncFileItem> SyncFileItemVector;
}
Q_DECLARE_METATYPE(Mirall::SyncFileItem)
#endif // SYNCFILEITEM_H

Просмотреть файл

@ -0,0 +1,120 @@
/*
* 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 <iostream>
#include <qcoreapplication.h>
#include <QStringList>
#include <QUrl>
#include <qdebug.h>
#include "csyncthread.h"
#include "csync.h"
using namespace Mirall;
int getauth(const char* prompt, char* buf, size_t len, int echo, int verify, void*)
{
std::cout << "AUTH CALLBACK\n" << prompt << std::endl;
std::string s;
std::getline(std::cin, s);
strncpy( buf, s.c_str(), len );
return 0;
}
void help() {
std::cout << "Usage: owncloudcmd [OPTION...] LOCAL REMOTE\n";
// " --dry-run This runs only update detection and reconcilation.\n"
// " --proxy=<host:port> Use an http proxy (ownCloud module only)\n"
}
struct ProxyInfo {
const char *proxyType;
const char *proxyHost;
int proxyPort;
const char *proxyUser;
const char *proxyPwd;
ProxyInfo() {
proxyType = 0;
proxyHost = 0;
proxyPort = 0;
proxyUser = 0;
proxyPwd = 0;
}
};
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
const char *source_dir = 0;
const char *target_url = 0;
const char *config_directory = 0;
ProxyInfo proxyInfo;
for (int i = 1; i < argc; ++i) {
if (argv[i][0] == '-') {
//TODO: parse arguments
std::cerr << "Argument not impemented " << argv[i] << std::endl;
} else if (!source_dir) {
source_dir = argv[i];
} else if (!target_url) {
target_url = argv[i];
} else if (!config_directory) {
config_directory = argv[i];
} else {
std::cerr << "Too many arguments" << std::endl;
return 1;
}
}
if (!source_dir || !target_url) {
std::cerr << "Too few arguments" << std::endl;
return 1;
}
CSYNC *_csync_ctx;
if( csync_create( &_csync_ctx, source_dir, target_url) < 0 ) {
qFatal("Unable to create csync-context!");
return EXIT_FAILURE;
}
//csync_set_log_callback( _csync_ctx, csyncLogCatcher );
csync_set_log_level(11);
csync_enable_conflictcopys(_csync_ctx);
csync_set_auth_callback( _csync_ctx, getauth );
if( csync_init( _csync_ctx ) < 0 ) {
qFatal("Could not initialize csync!");
return EXIT_FAILURE;
_csync_ctx = 0;
}
csync_set_module_property(_csync_ctx, "csync_context", _csync_ctx);
CSyncThread csyncthread(_csync_ctx, QString::fromLocal8Bit(source_dir), QUrl(target_url).path());
QObject::connect(&csyncthread, SIGNAL(finished()), &app, SLOT(quit()));
csyncthread.startSync();
app.exec();
csync_destroy(_csync_ctx);
return 0;
}

Просмотреть файл

@ -1,4 +1,6 @@
include_directories(${CMAKE_CURRENT_LIST_DIR}/../src)
include_directories(${CSYNC_INCLUDE_DIR}/csync ${CSYNC_INCLUDE_DIR} ${CSYNC_INCLUDE_DIR}/httpbf/src ${CSYNC_BUILD_PATH}/src)
include(owncloud_add_test.cmake)
owncloud_add_test(OwncloudPropagator)
owncloud_add_test(Utility)

Просмотреть файл

@ -0,0 +1,28 @@
/*
This software is in the public domain, furnished "as is", without technical
support, and with no warranty, express or implied, as to its usefulness for
any purpose.
*/
#ifndef MIRALL_TESTOWNCLOUDPROPAGATOR_H
#define MIRALL_TESTOWNCLOUDPROPAGATOR_H
#include <QtTest>
#include "mirall/owncloudpropagator.h"
using namespace Mirall;
class TestOwncloudPropagator : public QObject
{
Q_OBJECT
private slots:
void testUpdateErrorFromSession()
{
OwncloudPropagator propagator( NULL, QLatin1String("test1"), QLatin1String("test2"));
QVERIFY( true );
}
};
#endif