diff --git a/csync/src/csync_private.h b/csync/src/csync_private.h index 8552bf38c..d0ac60571 100644 --- a/csync/src/csync_private.h +++ b/csync/src/csync_private.h @@ -90,6 +90,11 @@ struct csync_s { csync_update_callback update_callback; void *update_callback_userdata; + /* hooks for checking the white list (uses the update_callback_userdata) */ + int (*checkSelectiveSyncBlackListHook)(void*, const char*); + int (*checkSelectiveSyncNewShareHook)(void*, const char*); + + csync_vio_opendir_hook remote_opendir_hook; csync_vio_readdir_hook remote_readdir_hook; csync_vio_closedir_hook remote_closedir_hook; @@ -165,9 +170,6 @@ struct csync_s { struct csync_owncloud_ctx_s *owncloud_context; - /* hooks for checking the white list */ - void *checkSelectiveSyncBlackListData; - int (*checkSelectiveSyncBlackListHook)(void*, const char*); }; diff --git a/csync/src/csync_update.c b/csync/src/csync_update.c index 692d8b7a5..c6c1d6e01 100644 --- a/csync/src/csync_update.c +++ b/csync/src/csync_update.c @@ -188,8 +188,8 @@ static int _csync_detect_update(CSYNC *ctx, const char *file, } } - if (ctx->current == REMOTE_REPLICA && ctx->checkSelectiveSyncBlackListHook) { - if (ctx->checkSelectiveSyncBlackListHook(ctx->checkSelectiveSyncBlackListData, path)) { + if (ctx->current == REMOTE_REPLICA && ctx->callbacks.checkSelectiveSyncBlackListHook) { + if (ctx->callbacks.checkSelectiveSyncBlackListHook(ctx->callbacks.update_callback_userdata, path)) { return 1; } } @@ -399,6 +399,15 @@ static int _csync_detect_update(CSYNC *ctx, const char *file, } else { /* file not found in statedb */ st->instruction = CSYNC_INSTRUCTION_NEW; + + if (fs->type == CSYNC_VIO_FILE_TYPE_DIRECTORY && ctx->current == REMOTE_REPLICA && ctx->callbacks.checkSelectiveSyncNewShareHook) { + if (strchr(fs->remotePerm, 'S') != NULL) { /* check that the directory is shared */ + if (ctx->callbacks.checkSelectiveSyncNewShareHook(ctx->callbacks.update_callback_userdata, path)) { + SAFE_FREE(st); + return 1; + } + } + } goto out; } } diff --git a/src/cmd/cmd.cpp b/src/cmd/cmd.cpp index 486cef3b2..0094d6742 100644 --- a/src/cmd/cmd.cpp +++ b/src/cmd/cmd.cpp @@ -251,38 +251,14 @@ void selectiveSyncFixup(OCC::SyncJournalDb *journal, const QStringList &newList) return; } - SqlQuery select("SELECT path FROM last_selective_sync", db); - QSet oldBlackListSet; - if (select.exec()) { - while(select.next()) { - oldBlackListSet.insert(select.stringValue(0)); - } - } - - + auto oldBlackListSet = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList).toSet(); auto blackListSet = newList.toSet(); auto changes = (oldBlackListSet - blackListSet) + (blackListSet - oldBlackListSet); foreach(const auto &it, changes) { journal->avoidReadFromDbOnNextSync(it); } - SqlQuery drop("DROP TABLE last_selective_sync", db); - drop.exec(); - - if (!newList.isEmpty()) { - SqlQuery createQuery(db); - createQuery.prepare("CREATE TABLE IF NOT EXISTS last_selective_sync(path VARCHAR(4096));"); - createQuery.exec(); - - SqlQuery insertQuery(db); - insertQuery.prepare("INSERT INTO last_selective_sync VALUES (?1);"); - - foreach(const auto &s, newList) { - insertQuery.reset(); - insertQuery.bindValue(1, s); - insertQuery.exec(); - } - } + journal->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, newList); } @@ -467,14 +443,14 @@ restart_sync: Cmd cmd; SyncJournalDb db(options.source_dir); - selectiveSyncFixup(&db, selectiveSyncList); + if (!selectiveSyncList.empty()) { + selectiveSyncFixup(&db, selectiveSyncList); + } SyncEngine engine(account, _csync_ctx, options.source_dir, QUrl(options.target_url).path(), folder, &db); QObject::connect(&engine, SIGNAL(finished()), &app, SLOT(quit())); QObject::connect(&engine, SIGNAL(transmissionProgress(ProgressInfo)), &cmd, SLOT(transmissionProgressSlot())); - engine.setSelectiveSyncBlackList(selectiveSyncList); - // Have to be done async, else, an error before exec() does not terminate the event loop. QMetaObject::invokeMethod(&engine, "startSync", Qt::QueuedConnection); diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index fa50a289b..8616e18ff 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -820,7 +820,6 @@ void Folder::startSync(const QStringList &pathList) connect(_engine.data(), SIGNAL(syncItemDiscovered(const SyncFileItem &)), this, SLOT(slotSyncItemDiscovered(const SyncFileItem &))); setDirtyNetworkLimits(); - _engine->setSelectiveSyncBlackList(selectiveSyncBlackList()); QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection); @@ -850,16 +849,6 @@ void Folder::setDirtyNetworkLimits() } } -void Folder::setSelectiveSyncBlackList(const QStringList& blackList) -{ - _selectiveSyncBlackList = blackList; - for (int i = 0; i < _selectiveSyncBlackList.count(); ++i) { - if (!_selectiveSyncBlackList.at(i).endsWith(QLatin1Char('/'))) { - _selectiveSyncBlackList[i].append(QLatin1Char('/')); - } - } -} - void Folder::slotSyncError(const QString& err) { diff --git a/src/gui/folder.h b/src/gui/folder.h index e2f47a2e1..06db2a0ab 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -131,9 +131,6 @@ public: // Used by the Socket API SyncJournalDb *journalDb() { return &_journal; } - QStringList selectiveSyncBlackList() { return _selectiveSyncBlackList; } - void setSelectiveSyncBlackList(const QStringList &blackList); - bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s); RequestEtagJob *etagJob() { return _requestEtagJob; } @@ -222,7 +219,6 @@ private: SyncResult _syncResult; QScopedPointer _engine; QStringList _errors; - QStringList _selectiveSyncBlackList; bool _csyncError; bool _csyncUnavail; bool _wipeDb; diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 6e3984754..ee2beb0f7 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -339,7 +339,6 @@ Folder* FolderMan::setupFolderFromConfigFile(const QString &file) { QString backend = settings.value(QLatin1String("backend")).toString(); QString targetPath = settings.value( QLatin1String("targetPath")).toString(); bool paused = settings.value( QLatin1String("paused"), false).toBool(); - QStringList blackList = settings.value( QLatin1String("blackList")).toStringList(); // QString connection = settings.value( QLatin1String("connection") ).toString(); QString alias = unescapeAlias( escapedAlias ); @@ -361,7 +360,6 @@ Folder* FolderMan::setupFolderFromConfigFile(const QString &file) { folder = new Folder( accountState, alias, path, targetPath, this ); folder->setConfigFile(cfgFile.absoluteFilePath()); - folder->setSelectiveSyncBlackList(blackList); qDebug() << "Adding folder to Folder Map " << folder; _folderMap[alias] = folder; if (paused) { @@ -378,6 +376,12 @@ Folder* FolderMan::setupFolderFromConfigFile(const QString &file) { _folderChangeSignalMapper->setMapping( folder, folder->alias() ); registerFolderMonitor(folder); + QStringList blackList = settings.value( QLatin1String("blackList")).toStringList(); + if (!blackList.empty()) { + //migrate settings + folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList); + settings.remove(QLatin1String("blackList")); + } return folder; } diff --git a/src/gui/selectivesyncdialog.cpp b/src/gui/selectivesyncdialog.cpp index 429fe9804..7a659e16a 100644 --- a/src/gui/selectivesyncdialog.cpp +++ b/src/gui/selectivesyncdialog.cpp @@ -358,7 +358,8 @@ SelectiveSyncDialog::SelectiveSyncDialog(AccountPtr account, Folder* folder, QWi : QDialog(parent, f), _folder(folder) { init(account, tr("Unchecked folders will be removed from your local file system and will not be synchronized to this computer anymore")); - _treeView->setFolderInfo(_folder->remotePath(), _folder->alias(), _folder->selectiveSyncBlackList()); + _treeView->setFolderInfo(_folder->remotePath(), _folder->alias(), + _folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList)); // Make sure we don't get crashes if the folder is destroyed while we are still open connect(_folder, SIGNAL(destroyed(QObject*)), this, SLOT(deleteLater())); @@ -396,14 +397,10 @@ void SelectiveSyncDialog::init(const AccountPtr &account, const QString &labelTe void SelectiveSyncDialog::accept() { if (_folder) { - auto oldBlackListSet = _folder->selectiveSyncBlackList().toSet(); + auto oldBlackListSet = _folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList).toSet(); QStringList blackList = _treeView->createBlackList(); - _folder->setSelectiveSyncBlackList(blackList); + _folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList); - // FIXME: Use ConfigFile - QSettings settings(_folder->configFile(), QSettings::IniFormat); - settings.beginGroup(FolderMan::escapeAlias(_folder->alias())); - settings.setValue("blackList", blackList); FolderMan *folderMan = FolderMan::instance(); if (_folder->isBusy()) { _folder->slotTerminateSync(); diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 31c8d07a8..1ac5b2e89 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -596,7 +596,7 @@ SyncFileStatus SocketApi::fileStatus(Folder *folder, const QString& systemFileNa } // Error if it is in the selective sync blacklistr - foreach(const auto &s, folder->selectiveSyncBlackList()) { + foreach(const auto &s, folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList)) { if (fileNameSlash.startsWith(s)) { return SyncFileStatus(SyncFileStatus::STATUS_ERROR); } diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 02d034d7a..096107a94 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -22,6 +22,29 @@ namespace OCC { + +/* Given a sorted list of paths ending with '/', return whether or not the given path is within one of the paths of the list*/ +static bool findPathInList(const QStringList &list, const QString &path) +{ + Q_ASSERT(std::is_sorted(list.begin(), list.end())); + QString pathSlash = path + QLatin1Char('/'); + + // Since the list is sorted, we can do a binary search. + // If the path is a prefix of another item or right after in the lexical order. + auto it = std::lower_bound(list.begin(), list.end(), pathSlash); + + if (it != list.end() && *it == pathSlash) { + return true; + } + + if (it == list.begin()) { + return false; + } + --it; + Q_ASSERT(it->endsWith(QLatin1Char('/'))); // Folder::setSelectiveSyncBlackList makes sure of that + return pathSlash.startsWith(*it); +} + bool DiscoveryJob::isInSelectiveSyncBlackList(const QString& path) const { if (_selectiveSyncBlackList.isEmpty()) { @@ -29,36 +52,60 @@ bool DiscoveryJob::isInSelectiveSyncBlackList(const QString& path) const return false; } - // If one of the item in the black list is a prefix of the path, it means this path need not to - // be synced. - // - // We know the list is sorted (for it is done in DiscoveryJob::start) - // So we can do a binary search. If the path is a prefix if another item or right after in the lexical order. + // Block if it is in the black list + return findPathInList(_selectiveSyncBlackList, path); - QString pathSlash = path + QLatin1Char('/'); - - auto it = std::lower_bound(_selectiveSyncBlackList.begin(), _selectiveSyncBlackList.end(), pathSlash); - - if (it != _selectiveSyncBlackList.end() && *it == pathSlash) { - return true; - } - - if (it == _selectiveSyncBlackList.begin()) { - return false; - } - --it; - Q_ASSERT(it->endsWith(QLatin1Char('/'))); // Folder::setSelectiveSyncBlackList makes sure of that - if (pathSlash.startsWith(*it)) { - return true; - } - return false; } -int DiscoveryJob::isInSelectiveSyncBlackListCallBack(void *data, const char *path) +int DiscoveryJob::isInSelectiveSyncBlackListCallback(void *data, const char *path) { return static_cast(data)->isInSelectiveSyncBlackList(QString::fromUtf8(path)); } +bool DiscoveryJob::checkSelectiveSyncNewShare(const QString& path) +{ + // If this path or the parent is in the white list, then we do not block this file + if (findPathInList(_selectiveSyncWhiteList, path)) { + return false; + } + + if (_newSharedFolderSizeLimit < 0) { + // no limit, everything is allowed; + return false; + } + + // Go in the main thread to do a PROPFIND to know the size of this directory + qint64 result = -1; + + { + QMutexLocker locker(&_vioMutex); + emit doGetSizeSignal(path, &result); + _vioWaitCondition.wait(&_vioMutex); + } + + auto limit = _newSharedFolderSizeLimit; + if (result > limit) { + // we tell the UI there is a new folder + emit newSharedFolder(path); + return true; + } else { + // it is not too big, put it in the white list (so we will not do more query for the children) + // and and do not block. + auto p = path; + if (!p.endsWith(QLatin1Char('/'))) { p += QLatin1Char('/'); } + _selectiveSyncWhiteList.insert(std::upper_bound(_selectiveSyncWhiteList.begin(), + _selectiveSyncWhiteList.end(), p), p); + + return false; + } +} + +int DiscoveryJob::checkSelectiveSyncNewShareCallback(void *data, const char *path) +{ + return static_cast(data)->checkSelectiveSyncNewShare(QString::fromUtf8(path)); +} + + void DiscoveryJob::update_job_update_callback (bool local, const char *dirUrl, void *userdata) @@ -319,6 +366,9 @@ void DiscoveryMainThread::setupHooks(DiscoveryJob *discoveryJob, const QString & connect(discoveryJob, SIGNAL(doOpendirSignal(QString,DiscoveryDirectoryResult*)), this, SLOT(doOpendirSlot(QString,DiscoveryDirectoryResult*)), Qt::QueuedConnection); + connect(discoveryJob, SIGNAL(doGetSizeSignal(QString,qint64*)), + this, SLOT(doGetSizeSlot(QString,qint64*)), + Qt::QueuedConnection); } // Coming from owncloud_opendir -> DiscoveryJob::vio_opendir_hook -> doOpendirSignal @@ -398,6 +448,60 @@ void DiscoveryMainThread::singleDirectoryJobFirstDirectoryPermissionsSlot(QStrin } } +void DiscoveryMainThread::doGetSizeSlot(const QString& path, qint64* result) +{ + QString fullPath = _pathPrefix; + if (!_pathPrefix.endsWith('/')) { + fullPath += '/'; + } + fullPath += path; + // remove trailing slash + while (fullPath.endsWith('/')) { + fullPath.chop(1); + } + + _currentGetSizeResult = result; + + // Schedule the DiscoverySingleDirectoryJob + auto propfindJob = new PropfindJob(_account, fullPath, this); + propfindJob->setProperties(QList() << "resourcetype" << "quota-used-bytes"); + QObject::connect(propfindJob, SIGNAL(finishedWithError()), + this, SLOT(slotGetSizeFinishedWithError())); + QObject::connect(propfindJob, SIGNAL(result(QVariantMap)), + this, SLOT(slotGetSizeResult(QVariantMap))); + propfindJob->start(); +} + +void DiscoveryMainThread::slotGetSizeFinishedWithError() +{ + if (! _currentGetSizeResult) { + return; // possibly aborted + } + + qWarning() << "Error getting the size of the directory"; + // just let let the discovery job continue then + _currentGetSizeResult = 0; + QMutexLocker locker(&_discoveryJob->_vioMutex); + _discoveryJob->_vioWaitCondition.wakeAll(); + +} + +void DiscoveryMainThread::slotGetSizeResult(const QVariantMap &map) +{ + if (! _currentGetSizeResult) { + return; // possibly aborted + } + + *_currentGetSizeResult = map.value(QLatin1String("quota-used-bytes")).toLongLong(); + qDebug() << "Size of folder:" << *_currentGetSizeResult; + _currentGetSizeResult = 0; + QMutexLocker locker(&_discoveryJob->_vioMutex); + _discoveryJob->_vioWaitCondition.wakeAll(); +} + + + + // called from SyncEngine void DiscoveryMainThread::abort() { if (_singleDirJob) { @@ -415,8 +519,11 @@ void DiscoveryMainThread::abort() { _discoveryJob->_vioMutex.unlock(); } } - - + if (_currentGetSizeResult) { + _currentGetSizeResult = 0; + QMutexLocker locker(&_discoveryJob->_vioMutex); + _discoveryJob->_vioWaitCondition.wakeAll(); + } } csync_vio_handle_t* DiscoveryJob::remote_vio_opendir_hook (const char *url, @@ -480,11 +587,10 @@ void DiscoveryJob::remote_vio_closedir_hook (csync_vio_handle_t *dhandle, void void DiscoveryJob::start() { _selectiveSyncBlackList.sort(); - _csync_ctx->checkSelectiveSyncBlackListHook = isInSelectiveSyncBlackListCallBack; - _csync_ctx->checkSelectiveSyncBlackListData = this; - - _csync_ctx->callbacks.update_callback = update_job_update_callback; _csync_ctx->callbacks.update_callback_userdata = this; + _csync_ctx->callbacks.update_callback = update_job_update_callback; + _csync_ctx->callbacks.checkSelectiveSyncBlackListHook = isInSelectiveSyncBlackListCallback; + _csync_ctx->callbacks.checkSelectiveSyncNewShareHook = checkSelectiveSyncNewShareCallback; _csync_ctx->callbacks.remote_opendir_hook = remote_vio_opendir_hook; _csync_ctx->callbacks.remote_readdir_hook = remote_vio_readdir_hook; @@ -497,9 +603,8 @@ void DiscoveryJob::start() { _lastUpdateProgressCallbackCall.invalidate(); int ret = csync_update(_csync_ctx); - _csync_ctx->checkSelectiveSyncBlackListHook = 0; - _csync_ctx->checkSelectiveSyncBlackListData = 0; - + _csync_ctx->callbacks.checkSelectiveSyncNewShareHook = 0; + _csync_ctx->callbacks.checkSelectiveSyncBlackListHook = 0; _csync_ctx->callbacks.update_callback = 0; _csync_ctx->callbacks.update_callback_userdata = 0; diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index 43dd644b6..21b8fbc1b 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -99,9 +99,10 @@ class DiscoveryMainThread : public QObject { QPointer _discoveryJob; QPointer _singleDirJob; - QString _pathPrefix; + QString _pathPrefix; // remote path AccountPtr _account; DiscoveryDirectoryResult *_currentDiscoveryDirectoryResult; + qint64 *_currentGetSizeResult; public: DiscoveryMainThread(AccountPtr account) : QObject(), _account(account), _currentDiscoveryDirectoryResult(0) { @@ -113,11 +114,15 @@ public: public slots: // From DiscoveryJob: void doOpendirSlot(QString url, DiscoveryDirectoryResult* ); + void doGetSizeSlot(const QString &path ,qint64 *result); // From Job: void singleDirectoryJobResultSlot(const QList &); void singleDirectoryJobFinishedWithErrorSlot(int csyncErrnoCode, QString msg); void singleDirectoryJobFirstDirectoryPermissionsSlot(QString); + + void slotGetSizeFinishedWithError(); + void slotGetSizeResult(const QVariantMap&); signals: void etagConcatenation(QString); public: @@ -137,11 +142,13 @@ class DiscoveryJob : public QObject { QElapsedTimer _lastUpdateProgressCallbackCall; /** - * return true if the given path should be synced, - * false if the path should be ignored + * return true if the given path should be ignored, + * false if the path should be synced */ bool isInSelectiveSyncBlackList(const QString &path) const; - static int isInSelectiveSyncBlackListCallBack(void *, const char *); + static int isInSelectiveSyncBlackListCallback(void *, const char *); + bool checkSelectiveSyncNewShare(const QString &path); + static int checkSelectiveSyncNewShareCallback(void*, const char*); // Just for progress static void update_job_update_callback (bool local, @@ -170,6 +177,8 @@ public: } QStringList _selectiveSyncBlackList; + QStringList _selectiveSyncWhiteList; + qint64 _newSharedFolderSizeLimit = 0; Q_INVOKABLE void start(); signals: void finished(int result); @@ -177,6 +186,10 @@ signals: // After the discovery job has been woken up again (_vioWaitCondition) void doOpendirSignal(QString url, DiscoveryDirectoryResult*); + void doGetSizeSignal(const QString &path, qint64 *result); + + // A new shared folder was discovered and was not synced because of the confirmation feature + void newSharedFolder(const QString &folder); }; } diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index 2262bb60c..4b14aebc3 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -711,7 +711,7 @@ bool PropfindJob::finished() QXmlStreamReader::TokenType type = reader.readNext(); if (type == QXmlStreamReader::StartElement) { if (!curElement.isEmpty() && curElement.top() == QLatin1String("prop")) { - items.insert(reader.name().toString(), reader.readElementText()); + items.insert(reader.name().toString(), reader.readElementText(QXmlStreamReader::SkipChildElements)); } else { curElement.push(reader.name().toString()); } diff --git a/src/libsync/ownsql.cpp b/src/libsync/ownsql.cpp index 6eb4cce9c..ba7405998 100644 --- a/src/libsync/ownsql.cpp +++ b/src/libsync/ownsql.cpp @@ -207,7 +207,7 @@ int SqlQuery::prepare( const QString& sql) if( _errId != SQLITE_OK ) { _error = QString::fromUtf8(sqlite3_errmsg(_db)); - qDebug() << "Sqlite prepare statement error:" << _error << "in" <<_sql; + qWarning() << "Sqlite prepare statement error:" << _error << "in" <<_sql; } } return _errId; @@ -260,6 +260,7 @@ bool SqlQuery::next() void SqlQuery::bindValue(int pos, const QVariant& value) { int res = -1; + Q_ASSERT(_stmt); if( _stmt ) { switch (value.type()) { case QVariant::Int: diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 7d845410f..cc65a6c58 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -606,7 +606,8 @@ void SyncEngine::startSync() // thereby speeding up the initial discovery significantly. _csync_ctx->db_is_empty = (fileRecordCount == 0); - bool usingSelectiveSync = (!_selectiveSyncBlackList.isEmpty()); + auto selectiveSyncBlackList = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList); + bool usingSelectiveSync = (!selectiveSyncBlackList.isEmpty()); qDebug() << (usingSelectiveSync ? "====Using Selective Sync" : "====NOT Using Selective Sync"); if (fileRecordCount >= 0 && fileRecordCount < 50 && !usingSelectiveSync) { qDebug() << "===== Activating recursive PROPFIND (currently" << fileRecordCount << "file records)"; @@ -636,12 +637,19 @@ void SyncEngine::startSync() DiscoveryJob *discoveryJob = new DiscoveryJob(_csync_ctx); - discoveryJob->_selectiveSyncBlackList = _selectiveSyncBlackList; + discoveryJob->_selectiveSyncBlackList = selectiveSyncBlackList; + discoveryJob->_selectiveSyncWhiteList = + _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList); + discoveryJob->_newSharedFolderSizeLimit = _newSharedFolderSizeLimit; discoveryJob->moveToThread(&_thread); connect(discoveryJob, SIGNAL(finished(int)), this, SLOT(slotDiscoveryJobFinished(int))); connect(discoveryJob, SIGNAL(folderDiscovered(bool,QString)), this, SIGNAL(folderDiscovered(bool,QString))); + connect(discoveryJob, SIGNAL(newSharedFolder(QString)), + this, SIGNAL(newSharedFolder(QString))); + + // This is used for the DiscoveryJob to be able to request the main thread/ // to read in directory contents. qDebug() << Q_FUNC_INFO << _remotePath << _remoteUrl; @@ -905,6 +913,9 @@ QString SyncEngine::adjustRenamedPath(const QString& original) */ void SyncEngine::checkForPermission() { + auto selectiveSyncBlackList = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList); + std::sort(selectiveSyncBlackList.begin(), selectiveSyncBlackList.end()); + for (SyncFileItemVector::iterator it = _syncedItems.begin(); it != _syncedItems.end(); ++it) { if ((*it)->_direction != SyncFileItem::Up) { // Currently we only check server-side permissions @@ -913,7 +924,7 @@ void SyncEngine::checkForPermission() // Do not propagate anything in the server if it is in the selective sync blacklist const QString path = (*it)->destination() + QLatin1Char('/'); - if (std::binary_search(_selectiveSyncBlackList.constBegin(), _selectiveSyncBlackList.constEnd(), + if (std::binary_search(selectiveSyncBlackList.constBegin(), selectiveSyncBlackList.constEnd(), path)) { (*it)->_instruction = CSYNC_INSTRUCTION_IGNORE; (*it)->_status = SyncFileItem::FileIgnored; @@ -1134,11 +1145,6 @@ QByteArray SyncEngine::getPermissions(const QString& file) const return _remotePerms.value(file); } -void SyncEngine::setSelectiveSyncBlackList(const QStringList& list) -{ - _selectiveSyncBlackList = list; -} - bool SyncEngine::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) { Q_UNUSED(t); diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index baa3ffcfd..838bf0646 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -62,9 +62,12 @@ public: /* Abort the sync. Called from the main thread */ void abort(); - Utility::StopWatch &stopWatch() { return _stopWatch; } + /* Set the maximum size a shared folder can have without asking for confirmation + * -1 means infinite + */ + void setNewSharedFolderSizeLimit(qint64 limit) { _newSharedFolderSizeLimit = limit; } - void setSelectiveSyncBlackList(const QStringList &list); + Utility::StopWatch &stopWatch() { return _stopWatch; } /* Return true if we detected that another sync is needed to complete the sync */ bool isAnotherSyncNeeded() { return _anotherSyncNeeded; } @@ -109,6 +112,9 @@ signals: void aboutToRemoveAllFiles(SyncFileItem::Direction direction, bool *cancel); + // A new shared folder was discovered and was not synced because of the confirmation feature + void newSharedFolder(const QString &folder); + private slots: void slotRootEtagReceived(QString); void slotJobCompleted(const SyncFileItem& item); @@ -196,12 +202,12 @@ private: int _uploadLimit; int _downloadLimit; + /* maximum size a shared folder can have without asking for confirmation: -1 means infinite */ + qint64 _newSharedFolderSizeLimit = -1; // hash containing the permissions on the remote directory QHash _remotePerms; - QStringList _selectiveSyncBlackList; - bool _anotherSyncNeeded; }; diff --git a/src/libsync/syncjournaldb.cpp b/src/libsync/syncjournaldb.cpp index 9298a120f..c3a45689f 100644 --- a/src/libsync/syncjournaldb.cpp +++ b/src/libsync/syncjournaldb.cpp @@ -261,6 +261,17 @@ bool SyncJournalDb::checkConnect() return sqlFail("Create table poll", createQuery); } + // create the selectivesync table. + createQuery.prepare("CREATE TABLE IF NOT EXISTS selectivesync (" + "path VARCHAR(4096)," + "type INTEGER" + ");"); + + if (!createQuery.exec()) { + return sqlFail("Create table selectivesync", createQuery); + } + + createQuery.prepare("CREATE TABLE IF NOT EXISTS version(" "major INTEGER(8)," @@ -396,6 +407,9 @@ bool SyncJournalDb::checkConnect() "(path, lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration) " "VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, ?7)"); + _getSelectiveSyncListQuery.reset(new SqlQuery(_db)); + _getSelectiveSyncListQuery->prepare("SELECT path FROM selectivesync WHERE type=?1"); + // don't start a new transaction now commitInternal(QString("checkConnect End"), false); @@ -1245,6 +1259,55 @@ void SyncJournalDb::setPollInfo(const SyncJournalDb::PollInfo& info) } } +QStringList SyncJournalDb::getSelectiveSyncList(SyncJournalDb::SelectiveSyncListType type) +{ + QStringList result; + + QMutexLocker locker(&_mutex); + if( !checkConnect() ) { + return result; + } + + _getSelectiveSyncListQuery->reset(); + _getSelectiveSyncListQuery->bindValue(1, int(type)); + if (!_getSelectiveSyncListQuery->exec()) { + qWarning() << "SQL query failed: "<< _getSelectiveSyncListQuery->error(); + return result; + } + while( _getSelectiveSyncListQuery->next() ) { + auto entry = _getSelectiveSyncListQuery->stringValue(0); + if (!entry.endsWith(QLatin1Char('/'))) { + entry.append(QLatin1Char('/')); + } + result.append(entry); + } + return result; +} + +void SyncJournalDb::setSelectiveSyncList(SyncJournalDb::SelectiveSyncListType type, const QStringList& list) +{ + QMutexLocker locker(&_mutex); + if( !checkConnect() ) { + return; + } + + //first, delete all entries of this type + SqlQuery delQuery("DELETE FROM selectivesync WHERE type == ?1", _db); + delQuery.bindValue(1, int(type)); + if( !delQuery.exec() ) { + qWarning() << "SQL error when deleting selective sync list" << list << delQuery.error(); + } + + SqlQuery insQuery("INSERT INTO selectivesync VALUES (?1, ?2)" , _db); + foreach(const auto &path, list) { + insQuery.bindValue(1, path); + insQuery.bindValue(2, int(type)); + if (!insQuery.exec()) { + qWarning() << "SQL error when inserting into selective sync" << type << path << delQuery.error(); + } + } +} + void SyncJournalDb::avoidRenamesOnNextSync(const QString& path) { QMutexLocker locker(&_mutex); diff --git a/src/libsync/syncjournaldb.h b/src/libsync/syncjournaldb.h index 6074631f1..5dce55700 100644 --- a/src/libsync/syncjournaldb.h +++ b/src/libsync/syncjournaldb.h @@ -91,6 +91,24 @@ public: void setPollInfo(const PollInfo &); QVector getPollInfos(); + enum SelectiveSyncListType { + /** The black list is the list of folders that are unselected in the selective sync dialog. + * For the sync engine, those folders are considered as if they were not there, so the local + * folders will be deleted */ + SelectiveSyncBlackList = 1, + /** When a shared flder has a size bigger than a configured size, it is by default not sync'ed + * Unless it is in the white list, in which case the folder is sync'ed and all its children. + * If a folder is both on the black and the white list, the black list wins */ + SelectiveSyncWhiteList = 2, + /** List of big sync folder that have not been confirmed by the user yet and that the UI + * should notify about */ + SelectiveSyncUndecidedList = 3 + }; + /* return the specified list from the database */ + QStringList getSelectiveSyncList(SelectiveSyncListType type); + /* Write the selective sync list (remove all other entries of that list */ + void setSelectiveSyncList(SelectiveSyncListType type, const QStringList &list); + /** * Make sure that on the next sync, filName is not read from the DB but use the PROPFIND to * get the info from the server @@ -140,6 +158,7 @@ private: QScopedPointer _deleteFileRecordRecursively; QScopedPointer _getErrorBlacklistQuery; QScopedPointer _setErrorBlacklistQuery; + QScopedPointer _getSelectiveSyncListQuery; /* This is the list of paths we called avoidReadFromDbOnNextSync on. * It means that they should not be written to the DB in any case since doing