dynamic chunking: cleanup, fixes, improvements
authorChristian Kamm <mail@ckamm.de>
Fri, 24 Mar 2017 14:01:50 +0000 (15:01 +0100)
committerChristian Kamm <mail@ckamm.de>
Tue, 28 Mar 2017 09:32:10 +0000 (11:32 +0200)
* make target duration a client option instead of a capability
* simplify algorithm for determining chunk size significantly
* preserve chunk size for the whole propagation, not just per upload
* move options to SyncOptions to avoid depending on ConfigFile
  in the propagator
* move chunk-size adjustment to after a chunk finishes, not when
  a new chunk starts

13 files changed:
src/gui/folder.cpp
src/gui/folder.h
src/libsync/capabilities.cpp
src/libsync/capabilities.h
src/libsync/configfile.cpp
src/libsync/configfile.h
src/libsync/discoveryphase.h
src/libsync/owncloudpropagator.cpp
src/libsync/owncloudpropagator.h
src/libsync/propagateupload.cpp
src/libsync/propagateupload.h
src/libsync/propagateuploadng.cpp
src/libsync/syncengine.cpp

index 18d2f2b12f7e8d386f037dfe9f896a30caa4d05f..7c82b6814bd5cb5da6a44c2d22328667b5b6deed 100644 (file)
@@ -645,19 +645,58 @@ void Folder::startSync(const QStringList &pathList)
     }
 
     setDirtyNetworkLimits();
+    setSyncOptions();
 
+    _engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles);
+
+    QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection);
+
+    emit syncStarted();
+}
+
+void Folder::setSyncOptions()
+{
     SyncOptions opt;
     ConfigFile cfgFile;
+
     auto newFolderLimit = cfgFile.newBigFolderSizeLimit();
     opt._newBigFolderSizeLimit = newFolderLimit.first ? newFolderLimit.second * 1000LL * 1000LL : -1; // convert from MB to B
     opt._confirmExternalStorage = cfgFile.confirmExternalStorage();
-    _engine->setSyncOptions(opt);
 
-    _engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles);
+    QByteArray chunkSizeEnv = qgetenv("OWNCLOUD_CHUNK_SIZE");
+    if (!chunkSizeEnv.isEmpty()) {
+        opt._initialChunkSize = chunkSizeEnv.toUInt();
+    } else {
+        opt._initialChunkSize = cfgFile.chunkSize();
+    }
+    QByteArray minChunkSizeEnv = qgetenv("OWNCLOUD_MIN_CHUNK_SIZE");
+    if (!minChunkSizeEnv.isEmpty()) {
+        opt._minChunkSize = minChunkSizeEnv.toUInt();
+    } else {
+        opt._minChunkSize = cfgFile.minChunkSize();
+    }
+    QByteArray maxChunkSizeEnv = qgetenv("OWNCLOUD_MAX_CHUNK_SIZE");
+    if (!maxChunkSizeEnv.isEmpty()) {
+        opt._maxChunkSize = maxChunkSizeEnv.toUInt();
+    } else {
+        opt._maxChunkSize = cfgFile.maxChunkSize();
+    }
 
-    QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection);
+    // Previously min/max chunk size values didn't exist, so users might
+    // have setups where the chunk size exceeds the new min/max default
+    // values. To cope with this, adjust min/max to always include the
+    // initial chunk size value.
+    opt._minChunkSize = qMin(opt._minChunkSize, opt._initialChunkSize);
+    opt._maxChunkSize = qMax(opt._maxChunkSize, opt._initialChunkSize);
 
-    emit syncStarted();
+    QByteArray targetChunkUploadDurationEnv = qgetenv("OWNCLOUD_TARGET_CHUNK_UPLOAD_DURATION");
+    if (!targetChunkUploadDurationEnv.isEmpty()) {
+        opt._targetChunkUploadDuration = targetChunkUploadDurationEnv.toUInt();
+    } else {
+        opt._targetChunkUploadDuration = cfgFile.targetChunkUploadDuration();
+    }
+
+    _engine->setSyncOptions(opt);
 }
 
 void Folder::setDirtyNetworkLimits()
index 67026bd52ed30c0ec92f12d9acc2513afa97b054..591f2de9e10a4d69e85b7dc43f63d18bf9c318a4 100644 (file)
@@ -307,6 +307,8 @@ private:
 
     void checkLocalPath();
 
+    void setSyncOptions();
+
     enum LogStatus {
         LogStatusRemove,
         LogStatusRename,
index 0c57eab1eae992c278abfb2a5205e8184b8e4d7a..cee5f1aebe2521bbc37c181e19c444f3bd0253e4 100644 (file)
@@ -130,12 +130,4 @@ QList<int> Capabilities::httpErrorCodesThatResetFailingChunkedUploads() const
     return list;
 }
 
-quint64 Capabilities::requestMaxDurationDC() const
-{
-    QByteArray requestMaxDurationDC = _capabilities["dav"].toMap()["max_single_upload_request_duration_msec"].toByteArray();
-    if (!requestMaxDurationDC.isEmpty())
-        return requestMaxDurationDC.toLongLong();
-    return 0;
-}
-
 }
index 0c28efa614516e150f0fd49f5e7aeeae6dc17d25..e3d8c013c238be6afc9a40236a3780e8117758ac 100644 (file)
@@ -41,7 +41,6 @@ public:
     int  sharePublicLinkExpireDateDays() const;
     bool shareResharing() const;
     bool chunkingNg() const;
-    quint64 requestMaxDurationDC() const;
 
     /// disable parallel upload in chunking
     bool chunkingParallelUploadDisabled() const;
index bad02f49ddd3a5fadd46b269cd3d2fb44c4d5497..bad5fd05d0edcf4643092c4afdf3fba74e0d56e3 100644 (file)
@@ -53,7 +53,9 @@ static const char updateCheckIntervalC[] = "updateCheckInterval";
 static const char geometryC[] = "geometry";
 static const char timeoutC[] = "timeout";
 static const char chunkSizeC[] = "chunkSize";
-static const char maxChunkSizeC[] = "maxChunkSizeC";
+static const char minChunkSizeC[] = "minChunkSize";
+static const char maxChunkSizeC[] = "maxChunkSize";
+static const char targetChunkUploadDurationC[] = "targetChunkUploadDuration";
 
 static const char proxyHostC[] = "Proxy/host";
 static const char proxyTypeC[] = "Proxy/type";
@@ -134,13 +136,19 @@ quint64 ConfigFile::chunkSize() const
 quint64 ConfigFile::maxChunkSize() const
 {
     QSettings settings(configFile(), QSettings::IniFormat);
-    return settings.value(QLatin1String(maxChunkSizeC), 50*1000*1000).toLongLong(); // default to 50 MB
+    return settings.value(QLatin1String(maxChunkSizeC), 100*1000*1000).toLongLong(); // default to 100 MB
 }
 
 quint64 ConfigFile::minChunkSize() const
 {
     QSettings settings(configFile(), QSettings::IniFormat);
-    return settings.value(QLatin1String(maxChunkSizeC), 1000*1000).toLongLong(); // default to 1 MB
+    return settings.value(QLatin1String(minChunkSizeC), 1000*1000).toLongLong(); // default to 1 MB
+}
+
+quint64 ConfigFile::targetChunkUploadDuration() const
+{
+    QSettings settings(configFile(), QSettings::IniFormat);
+    return settings.value(QLatin1String(targetChunkUploadDurationC), 60*1000).toLongLong(); // default to 1 minute
 }
 
 void ConfigFile::setOptionalDesktopNotifications(bool show)
index 8191ab13e6e121a71700ccd8160d65a1e9384874..2f3a6beccc92a066c7aaa6a3ba5d77f49df3cc04 100644 (file)
@@ -117,6 +117,7 @@ public:
     quint64 chunkSize() const;
     quint64 maxChunkSize() const;
     quint64 minChunkSize() const;
+    quint64 targetChunkUploadDuration() const;
 
     void saveGeometry(QWidget *w);
     void restoreGeometry(QWidget *w);
index 3bccf6a4a1399fdf22e189983f2dbdd598e53729..e34e9505e4556655267d6b073d8f221dd3117bea 100644 (file)
@@ -35,12 +35,41 @@ class Account;
  */
 
 struct SyncOptions {
-    SyncOptions() : _newBigFolderSizeLimit(-1), _confirmExternalStorage(false) {}
+    SyncOptions()
+        : _newBigFolderSizeLimit(-1)
+        , _confirmExternalStorage(false)
+        , _initialChunkSize(10 * 1000 * 1000) // 10 MB
+        , _minChunkSize(1 * 1000 * 1000) // 1 MB
+        , _maxChunkSize(100 * 1000 * 1000) // 100 MB
+        , _targetChunkUploadDuration(60 * 1000) // 1 minute
+    {}
+
     /** Maximum size (in Bytes) a folder can have without asking for confirmation.
      * -1 means infinite */
     qint64 _newBigFolderSizeLimit;
+
     /** If a confirmation should be asked for external storages */
     bool _confirmExternalStorage;
+
+    /** The initial un-adjusted chunk size in bytes for chunked uploads
+     *
+     * When dynamic chunk size adjustments are done, this is the
+     * starting value and is then gradually adjusted within the
+     * minChunkSize / maxChunkSize bounds.
+     */
+    quint64 _initialChunkSize;
+
+    /** The minimum chunk size in bytes for chunked uploads */
+    quint64 _minChunkSize;
+
+    /** The maximum chunk size in bytes for chunked uploads */
+    quint64 _maxChunkSize;
+
+    /** The target duration of chunk uploads for dynamic chunk sizing.
+     *
+     * Set to 0 it will disable dynamic chunk sizing.
+     */
+    quint64 _targetChunkUploadDuration;
 };
 
 
index 3c79a87f3e592f05c68472edb2dac630aefff9c5..232984e048811e57e38386b88f00697342a78b4e 100644 (file)
@@ -369,8 +369,8 @@ PropagateItemJob* OwncloudPropagator::createJob(const SyncFileItemPtr &item) {
                 return job;
             } else {
                 PropagateUploadFileCommon *job = 0;
-                if (item->_size > chunkSize() && account()->capabilities().chunkingNg()) {
-                    job = new PropagateUploadFileNG(this, item, account()->capabilities().requestMaxDurationDC());
+                if (item->_size > _chunkSize && account()->capabilities().chunkingNg()) {
+                    job = new PropagateUploadFileNG(this, item);
                 } else {
                     job = new PropagateUploadFileV1(this, item);
                 }
@@ -503,6 +503,17 @@ void OwncloudPropagator::start(const SyncFileItemVector& items)
     scheduleNextJob();
 }
 
+const SyncOptions& OwncloudPropagator::syncOptions() const
+{
+    return _syncOptions;
+}
+
+void OwncloudPropagator::setSyncOptions(const SyncOptions& syncOptions)
+{
+    _syncOptions = syncOptions;
+    _chunkSize = syncOptions._initialChunkSize;
+}
+
 // ownCloud server  < 7.0 did not had permissions so we need some other euristics
 // to detect wrong doing in a Shared directory
 bool OwncloudPropagator::isInSharedDirectory(const QString& file)
@@ -522,7 +533,7 @@ bool OwncloudPropagator::isInSharedDirectory(const QString& file)
 
 int OwncloudPropagator::httpTimeout()
 {
-    static int timeout;
+    static int timeout = 0;
     if (!timeout) {
         timeout = qgetenv("OWNCLOUD_TIMEOUT").toUInt();
         if (timeout == 0) {
@@ -534,32 +545,6 @@ int OwncloudPropagator::httpTimeout()
     return timeout;
 }
 
-quint64 OwncloudPropagator::chunkSize()
-{
-    static uint chunkSize;
-    if (!chunkSize) {
-        chunkSize = qgetenv("OWNCLOUD_CHUNK_SIZE").toUInt();
-        if (chunkSize == 0) {
-            ConfigFile cfg;
-            chunkSize = cfg.chunkSize();
-        }
-    }
-    return chunkSize;
-}
-
-quint64 OwncloudPropagator::maxChunkSize()
-{
-    static uint chunkSize;
-    if (!chunkSize) {
-        chunkSize = qgetenv("OWNCLOUD_MAX_CHUNK_SIZE").toUInt();
-        if (chunkSize == 0) {
-            ConfigFile cfg;
-            chunkSize = cfg.maxChunkSize();
-        }
-    }
-    return chunkSize;
-}
-
 bool OwncloudPropagator::localFileNameClash( const QString& relFile )
 {
     bool re = false;
index be913b75386b4cfa336103596d4b5551fdd8ea3f..b2a57f7da0a970a11d1957955311cf1c2dacfacf 100644 (file)
@@ -29,6 +29,7 @@
 #include "syncjournaldb.h"
 #include "bandwidthmanager.h"
 #include "accountfwd.h"
+#include "discoveryphase.h"
 
 namespace OCC {
 
@@ -288,6 +289,7 @@ public:
             , _finishedEmited(false)
             , _bandwidthManager(this)
             , _anotherSyncNeeded(false)
+            , _chunkSize(10 * 1000 * 1000) // 10 MB, overridden in setSyncOptions
             , _account(account)
     { }
 
@@ -295,6 +297,9 @@ public:
 
     void start(const SyncFileItemVector &_syncedItems);
 
+    const SyncOptions& syncOptions() const;
+    void setSyncOptions(const SyncOptions& syncOptions);
+
     QAtomicInt _downloadLimit;
     QAtomicInt _uploadLimit;
     BandwidthManager _bandwidthManager;
@@ -314,6 +319,15 @@ public:
 
     /* the maximum number of jobs using bandwidth (uploads or downloads, in parallel) */
     int maximumActiveTransferJob();
+
+    /** The size to use for upload chunks.
+     *
+     * Will be dynamically adjusted after each chunk upload finishes
+     * if Capabilities::desiredChunkUploadDuration has a target
+     * chunk-upload duration set.
+     */
+    quint64 _chunkSize;
+
     /* The maximum number of active jobs in parallel  */
     int hardMaximumActiveJob();
 
@@ -354,10 +368,6 @@ public:
     // timeout in seconds
     static int httpTimeout();
 
-    /** returns the size of chunks in bytes  */
-    static quint64 chunkSize();
-    static quint64 maxChunkSize();
-
     AccountPtr account() const;
 
     enum DiskSpaceResult
@@ -404,6 +414,7 @@ private:
 
     AccountPtr _account;
     QScopedPointer<PropagateDirectory> _rootJob;
+    SyncOptions _syncOptions;
 
 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
     // access to signals which are protected in Qt4
index 56a46d4e4f27798b301046c9dad1691a31f98965..62dd386fd1e4e6141f89d1da8bf737ead7596723 100644 (file)
@@ -97,6 +97,7 @@ void PUTFileJob::start() {
         connect(_device, SIGNAL(wasReset()), this, SLOT(slotSoftAbort()));
 #endif
 
+    _requestTimer.start();
     AbstractNetworkJob::start();
 }
 
index cf6e5b7956fbb8595458083cca1b645a13437323..c031602bc9010f6bdfeb3ea3c8fa6ce3b265293a 100644 (file)
@@ -19,6 +19,7 @@
 #include <QBuffer>
 #include <QFile>
 #include <QDebug>
+#include <QElapsedTimer>
 
 
 namespace OCC {
@@ -90,6 +91,7 @@ private:
     QMap<QByteArray, QByteArray> _headers;
     QString _errorString;
     QUrl _url;
+    QElapsedTimer _requestTimer;
 
 public:
     // Takes ownership of the device
@@ -123,6 +125,10 @@ public:
 
     virtual void slotTimeout() Q_DECL_OVERRIDE;
 
+    quint64 msSinceStart() const {
+        return _requestTimer.elapsed();
+    }
+
 
 signals:
     void finishedSignal();
@@ -201,7 +207,6 @@ protected:
     QByteArray _transmissionChecksum;
     QByteArray _transmissionChecksumType;
 
-
 public:
     PropagateUploadFileCommon(OwncloudPropagator* propagator,const SyncFileItemPtr& item)
         : PropagateItemJob(propagator, item), _finished(false), _deleteExisting(false) {}
@@ -276,7 +281,7 @@ private:
     int _chunkCount; /// Total number of chunks for this file
     int _transferId; /// transfer id (part of the url)
 
-    quint64 chunkSize() const { return propagator()->chunkSize(); }
+    quint64 chunkSize() const { return propagator()->syncOptions()._initialChunkSize; }
 
 
 public:
@@ -303,38 +308,14 @@ private:
     quint64 _sent; /// amount of data (bytes) that was already sent
     uint _transferId; /// transfer id (part of the url)
     int _currentChunk; /// Id of the next chunk that will be sent
+    quint64 _currentChunkSize; /// current chunk size
     bool _removeJobError; /// If not null, there was an error removing the job
-    quint64 _lastChunkSize; /// current chunk size
-
-    /*
-    * This is value in ms obtained from the server.
-    *
-    * Dynamic Chunking attribute the maximum number of miliseconds that single request below chunk size can take
-    * This value should be based on heuristics with default value 10000ms, time it takes to transfer 10MB chunk on 1MB/s upload link.
-    *
-    * Suggested solution will be to evaluate max(SNR, MORD) where:
-    * > SNR - Slow network request, so time it will take to transmit default chunking sized request at specific low upload bandwidth
-    * > MORD - Maximum observed request time, so double the time of maximum observed RTT of the very small PUT request (e.g. 1kB) to the system
-    *
-    * Exemplary, syncing 100MB files, with chunking size 10MB, will cause sync of 10 PUT requests which max evaluation was set to <max_single_upload_request_duration_msec>
-    *
-    * Dynamic chunking client algorithm is specified in the ownCloud documentation and uses <max_single_upload_request_duration_msec> to estimate if given
-    * bandwidth allows higher chunk sizes (because of high goodput)
-    */
-    quint64 _requestMaxDuration;
 
     // Map chunk number with its size  from the PROPFIND on resume.
     // (Only used from slotPropfindIterate/slotPropfindFinished because the LsColJob use signals to report data.)
     struct ServerChunkInfo { quint64 size; QString originalName; };
     QMap<int, ServerChunkInfo> _serverChunks;
 
-    quint64 chunkSize() const { return propagator()->chunkSize(); }
-    quint64 maxChunkSize() const { return propagator()->maxChunkSize(); }
-
-    quint64 getRequestMaxDurationDC(){
-        return _requestMaxDuration;
-    }
-
     /**
      * Return the URL of a chunk.
      * If chunk == -1, returns the URL of the parent folder containing the chunks
@@ -342,8 +323,8 @@ private:
     QUrl chunkUrl(int chunk = -1);
 
 public:
-    PropagateUploadFileNG(OwncloudPropagator* propagator,const SyncFileItemPtr& item, const quint64& requestMaxDuration) :
-        PropagateUploadFileCommon(propagator,item), _lastChunkSize(0), _requestMaxDuration(requestMaxDuration) {}
+    PropagateUploadFileNG(OwncloudPropagator* propagator,const SyncFileItemPtr& item) :
+        PropagateUploadFileCommon(propagator,item), _currentChunkSize(0) {}
 
     void doStartUpload() Q_DECL_OVERRIDE;
 
index 63d71d3c4d9a4399b87043c538abed489593e6db..300c0eec1876d8cf993c1260d8ed5332b86f86c1 100644 (file)
@@ -32,7 +32,7 @@
 #include <QDir>
 #include <cmath>
 #include <cstring>
-#include <cmath>
+
 namespace OCC {
 
 QUrl PropagateUploadFileNG::chunkUrl(int chunk)
@@ -272,44 +272,11 @@ void PropagateUploadFileNG::startNextChunk()
     quint64 fileSize = _item->_size;
     ENFORCE(fileSize >= _sent, "Sent data exceeds file size");
 
-    quint64 currentChunkSize = chunkSize();
-
-    // this will check if getRequestMaxDurationDC is set to 0 or not
-    double requestMaxDurationDC = (double) getRequestMaxDurationDC();
-    if (requestMaxDurationDC != 0) {
-        // this if first chunked file request, so it can start with default size of chunkSize()
-        // if _lastChunkSize != 0 it means that we already have send one request
-        if(_lastChunkSize != 0){
-            //TODO: this is done step by step for debugging purposes
-
-            //get last request timestamp
-            double lastChunkLap = (double) _stopWatch.durationOfLap(QLatin1String("ChunkDuration"));
-
-            //get duration of the request
-            double requestDuration = (double) _stopWatch.addLapTime(QLatin1String("ChunkDuration")) - lastChunkLap;
-
-            // calculate natural logarithm
-            double correctionParameter = log(requestMaxDurationDC / requestDuration) - 1;
-
-            // If logarithm is smaller or equal zero, it means that we exceeded max request duration
-            // If exceeded it will use currentChunkSize = chunkSize()
-            // If did not exceeded, we will increase the chunk size
-            // motivation for logarithm is specified in the dynamic chunking documentation
-            // TODO: give link to documentation
-            if (correctionParameter>0){
-                currentChunkSize = qMin(_lastChunkSize + (qint64) correctionParameter*chunkSize(), maxChunkSize());
-            }
-        }
-
-        //remember the value of last chunk size
-        _lastChunkSize = currentChunkSize;
-    }
-
     // prevent situation that chunk size is bigger then required one to send
-    currentChunkSize = qMin(currentChunkSize, fileSize - _sent);
+    _currentChunkSize = qMin(propagator()->_chunkSize, fileSize - _sent);
 
-    if (currentChunkSize == 0) {
-        ASSERT(_jobs.isEmpty());
+    if (_currentChunkSize == 0) {
+        Q_ASSERT(_jobs.isEmpty()); // There should be no running job anymore
         _finished = true;
         // Finish with a MOVE
         QString destination = QDir::cleanPath(propagator()->account()->url().path() + QLatin1Char('/')
@@ -339,7 +306,7 @@ void PropagateUploadFileNG::startNextChunk()
     auto device = new UploadDevice(&propagator()->_bandwidthManager);
     const QString fileName = propagator()->getFilePath(_item->_file);
 
-    if (! device->prepareAndOpen(fileName, _sent, currentChunkSize)) {
+    if (! device->prepareAndOpen(fileName, _sent, _currentChunkSize)) {
         qDebug() << "ERR: Could not prepare upload device: " << device->errorString();
 
         // If the file is currently locked, we want to retry the sync
@@ -355,7 +322,7 @@ void PropagateUploadFileNG::startNextChunk()
     QMap<QByteArray, QByteArray> headers;
     headers["OC-Chunk-Offset"] = QByteArray::number(_sent);
 
-    _sent += currentChunkSize;
+    _sent += _currentChunkSize;
     QUrl url = chunkUrl(_currentChunk);
 
     // job takes ownership of device via a QScopedPointer. Job deletes itself when finishing
@@ -428,6 +395,37 @@ void PropagateUploadFileNG::slotPutFinished()
     }
 
     ENFORCE(_sent <= _item->_size, "can't send more than size");
+
+    // Adjust the chunk size for the time taken.
+    //
+    // Dynamic chunk sizing is enabled if the server configured a
+    // target duration for each chunk upload.
+    double targetDuration = propagator()->syncOptions()._targetChunkUploadDuration;
+    if (targetDuration > 0) {
+        double uploadTime = job->msSinceStart();
+
+        auto predictedGoodSize = static_cast<quint64>(
+                _currentChunkSize / uploadTime * targetDuration);
+
+        // The whole targeting is heuristic. The predictedGoodSize will fluctuate
+        // quite a bit because of external factors (like available bandwidth)
+        // and internal factors (like number of parallel uploads).
+        //
+        // We use an exponential moving average here as a cheap way of smoothing
+        // the chunk sizes a bit.
+        quint64 targetSize = (propagator()->_chunkSize + predictedGoodSize) / 2;
+
+        propagator()->_chunkSize = qBound(
+                propagator()->syncOptions()._minChunkSize,
+                targetSize,
+                propagator()->syncOptions()._maxChunkSize);
+
+        qDebug() << "Chunked upload of" << _currentChunkSize << "bytes took" << uploadTime
+                 << "ms, desired is" << targetDuration << "ms, expected good chunk size is"
+                 << predictedGoodSize << "bytes and nudged next chunk size to "
+                 << propagator()->_chunkSize << "bytes";
+    }
+
     bool finished = _sent == _item->_size;
 
     // Check if the file still exists
index 5aa2376b0a59848ea21cce431d906c5decab5ac0..623734a83539e298bd7bc6b0d3e84791d103ae67 100644 (file)
@@ -1004,6 +1004,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
 
     _propagator = QSharedPointer<OwncloudPropagator>(
         new OwncloudPropagator (_account, _localPath, _remotePath, _journal));
+    _propagator->setSyncOptions(_syncOptions);
     connect(_propagator.data(), SIGNAL(itemCompleted(const SyncFileItemPtr &)),
             this, SLOT(slotItemCompleted(const SyncFileItemPtr &)));
     connect(_propagator.data(), SIGNAL(progress(const SyncFileItem &,quint64)),