зеркало из https://github.com/nextcloud/desktop.git
Merge branch 'propagator-ng': Fixes to work with new LGPL ocsync.
Conflicts: src/mirall/csyncthread.cpp
This commit is contained in:
Коммит
8fe102662d
|
@ -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 )
|
153
doc/Makefile
153
doc/Makefile
|
@ -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
|
Загрузка…
Ссылка в новой задаче