From df386b64ba88c70f13cae2514e3dacda8540fb6a Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Wed, 16 Mar 2016 19:07:40 +0100 Subject: [PATCH 01/17] Make the AccountState a construction argument of the Folder This will help moving the SyncEngine construction in the constructor and allow moving functionalities from Folder to SyncEngine or its delegated objects. --- src/gui/accountmanager.h | 2 -- src/gui/accountstate.h | 6 ++-- src/gui/folder.cpp | 56 +++------------------------------ src/gui/folder.h | 7 ++--- src/gui/folderman.cpp | 16 ++++------ src/gui/folderman.h | 2 +- src/gui/owncloudgui.cpp | 2 +- src/gui/owncloudgui.h | 1 - src/gui/selectivesyncdialog.cpp | 13 ++------ src/libsync/accountfwd.h | 2 +- src/libsync/excludedfiles.cpp | 22 +++++++++---- src/libsync/excludedfiles.h | 12 +++---- test/testfolderman.h | 6 ++-- 13 files changed, 48 insertions(+), 99 deletions(-) diff --git a/src/gui/accountmanager.h b/src/gui/accountmanager.h index 321a6ec389..c500ef51e4 100644 --- a/src/gui/accountmanager.h +++ b/src/gui/accountmanager.h @@ -18,8 +18,6 @@ namespace OCC { -typedef QSharedPointer AccountStatePtr; - /** @brief The AccountManager class @ingroup gui diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index d06262a56c..6ba0add894 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -29,11 +29,13 @@ namespace OCC { class AccountState; class Account; +typedef QExplicitlySharedDataPointer AccountStatePtr; + /** * @brief Extra info about an ownCloud server account. * @ingroup gui */ -class AccountState : public QObject { +class AccountState : public QObject, public QSharedData { Q_OBJECT public: enum State { @@ -148,6 +150,6 @@ private: } Q_DECLARE_METATYPE(OCC::AccountState*) -Q_DECLARE_METATYPE(QSharedPointer) +Q_DECLARE_METATYPE(OCC::AccountStatePtr) #endif //ACCOUNTINFO_H diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 5935aad9cb..d94552b399 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -56,9 +56,10 @@ static void csyncLogCatcher(int /*verbosity*/, Folder::Folder(const FolderDefinition& definition, + AccountState* accountState, QObject* parent) : QObject(parent) - , _accountState(0) + , _accountState(accountState) , _definition(definition) , _csyncError(false) , _csyncUnavail(false) @@ -94,16 +95,6 @@ Folder::~Folder() } } -void Folder::setAccountState( AccountState *account ) -{ - _accountState = account; -} - -AccountState* Folder::accountState() const -{ - return _accountState; -} - void Folder::checkLocalPath() { const QFileInfo fi(_definition.localPath); @@ -202,9 +193,6 @@ QString Folder::remotePath() const QUrl Folder::remoteUrl() const { - if (!_accountState) { - return QUrl("http://deleted-account"); - } return Account::concatUrlPath(_accountState->account()->davUrl(), remotePath()); } @@ -256,11 +244,6 @@ void Folder::slotRunEtagJob() { qDebug() << "* Trying to check" << remoteUrl().toString() << "for changes via ETag check. (time since last sync:" << (_timeSinceLastSyncDone.elapsed() / 1000) << "s)"; - if (!_accountState) { - qDebug() << "Can't run EtagJob, account is deleted"; - return; - } - AccountPtr account = _accountState->account(); if (!_requestEtagJob.isNull()) { @@ -328,9 +311,7 @@ void Folder::etagRetreived(const QString& etag) emit scheduleToSync(this); } - if( _accountState ) { - _accountState->tagLastSuccessfullETagRequest(); - } + _accountState->tagLastSuccessfullETagRequest(); } void Folder::etagRetreivedFromSyncEngine(const QString& etag) @@ -665,11 +646,6 @@ bool Folder::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) void Folder::saveToSettings() const { - if (!_accountState) { - qDebug() << "Can't save folder to settings, account is deleted"; - return; - } - auto settings = _accountState->settings(); settings->beginGroup(QLatin1String("Folders")); FolderDefinition::save(*settings, _definition); @@ -680,11 +656,6 @@ void Folder::saveToSettings() const void Folder::removeFromSettings() const { - if (!_accountState) { - qDebug() << "Can't remove folder from settings, account is deleted"; - return; - } - auto settings = _accountState->settings(); settings->beginGroup(QLatin1String("Folders")); settings->remove(_definition.alias); @@ -692,24 +663,12 @@ void Folder::removeFromSettings() const bool Folder::isFileExcludedAbsolute(const QString& fullPath) const { - if (!fullPath.startsWith(path())) { - // Mark paths we're not responsible for as excluded... - return true; - } - - QString myFullPath = fullPath; - if (myFullPath.endsWith(QLatin1Char('/'))) { - myFullPath.chop(1); - } - - QString relativePath = myFullPath.mid(path().size()); - auto excl = ExcludedFiles::instance().isExcluded(myFullPath, relativePath, _definition.ignoreHiddenFiles); - return excl != CSYNC_NOT_EXCLUDED; + return ExcludedFiles::instance().isExcluded(fullPath, path(), _definition.ignoreHiddenFiles); } bool Folder::isFileExcludedRelative(const QString& relativePath) const { - return isFileExcludedAbsolute(path() + relativePath); + return ExcludedFiles::instance().isExcluded(path() + relativePath, path(), _definition.ignoreHiddenFiles); } void Folder::watcherSlot(QString fn) @@ -814,11 +773,6 @@ bool Folder::proxyDirty() void Folder::startSync(const QStringList &pathList) { - if (!_accountState) { - qDebug() << "Can't startSync, account is deleted"; - return; - } - Q_UNUSED(pathList) if (proxyDirty()) { setProxyDirty(false); diff --git a/src/gui/folder.h b/src/gui/folder.h index cb13e8d8c1..8a7976ac71 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -87,7 +87,7 @@ class Folder : public QObject Q_OBJECT public: - Folder(const FolderDefinition& definition, QObject* parent = 0L); + Folder(const FolderDefinition& definition, AccountState* accountState, QObject* parent = 0L); ~Folder(); @@ -97,8 +97,7 @@ public: /** * The account the folder is configured on. */ - void setAccountState( AccountState *account ); - AccountState* accountState() const; + AccountState* accountState() const { return _accountState.data(); } /** * alias or nickname @@ -281,7 +280,7 @@ private: void createGuiLog(const QString& filename, SyncFileStatus status, int count, const QString& renameTarget = QString::null ); - QPointer _accountState; + AccountStatePtr _accountState; FolderDefinition _definition; SyncResult _syncResult; diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 85bb94a47f..4215ae0c54 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -206,9 +206,8 @@ int FolderMan::setupFolders() foreach (const auto& folderAlias, settings->childGroups()) { FolderDefinition folderDefinition; if (FolderDefinition::load(*settings, folderAlias, &folderDefinition)) { - Folder* f = addFolderInternal(folderDefinition); + Folder* f = addFolderInternal(folderDefinition, account.data()); if (f) { - f->setAccountState( account.data() ); slotScheduleSync(f); emit folderSyncStateChange(f); } @@ -395,10 +394,8 @@ Folder* FolderMan::setupFolderFromOldConfigFile(const QString &file, AccountStat folderDefinition.paused = paused; folderDefinition.ignoreHiddenFiles = ignoreHiddenFiles(); - folder = addFolderInternal(folderDefinition); + folder = addFolderInternal(folderDefinition, accountState); if (folder) { - folder->setAccountState(accountState); - QStringList blackList = settings.value( QLatin1String("blackList")).toStringList(); if (!blackList.empty()) { //migrate settings @@ -786,9 +783,8 @@ Folder* FolderMan::addFolder(AccountState* accountState, const FolderDefinition& return 0; } - auto folder = addFolderInternal(folderDefinition); - if(folder && accountState) { - folder->setAccountState(accountState); + auto folder = addFolderInternal(folderDefinition, accountState); + if(folder) { folder->saveToSettings(); emit folderSyncStateChange(folder); emit folderListChanged(_folderMap); @@ -796,9 +792,9 @@ Folder* FolderMan::addFolder(AccountState* accountState, const FolderDefinition& return folder; } -Folder* FolderMan::addFolderInternal(const FolderDefinition& folderDefinition) +Folder* FolderMan::addFolderInternal(const FolderDefinition& folderDefinition, AccountState* accountState) { - auto folder = new Folder(folderDefinition, this ); + auto folder = new Folder(folderDefinition, accountState, this ); qDebug() << "Adding folder to Folder Map " << folder; _folderMap[folder->alias()] = folder; diff --git a/src/gui/folderman.h b/src/gui/folderman.h index c8fe41374a..59b467acf1 100644 --- a/src/gui/folderman.h +++ b/src/gui/folderman.h @@ -202,7 +202,7 @@ private: /** Adds a new folder, does not add it to the account settings and * does not set an account on the new folder. */ - Folder* addFolderInternal(const FolderDefinition& folderDefinition); + Folder* addFolderInternal(const FolderDefinition& folderDefinition, AccountState* accountState); /* unloads a folder object, does not delete it */ void unloadFolder( Folder * ); diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index d5005cc9c6..944b8ac407 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -350,7 +350,7 @@ void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *men bool onePaused = false; bool allPaused = true; foreach (Folder* folder, folderMan->map()) { - if (folder->accountState() != accountState) { + if (folder->accountState() != accountState.data()) { continue; } diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h index 9405fa577b..74f658ba2f 100644 --- a/src/gui/owncloudgui.h +++ b/src/gui/owncloudgui.h @@ -35,7 +35,6 @@ class ShareDialog; class Application; class LogBrowser; class AccountState; -typedef QSharedPointer AccountStatePtr; /** * @brief The ownCloudGui class diff --git a/src/gui/selectivesyncdialog.cpp b/src/gui/selectivesyncdialog.cpp index a6a80bd472..62461740c4 100644 --- a/src/gui/selectivesyncdialog.cpp +++ b/src/gui/selectivesyncdialog.cpp @@ -14,6 +14,7 @@ #include "selectivesyncdialog.h" #include "folder.h" #include "account.h" +#include "excludedfiles.h" #include "networkjobs.h" #include "theme.h" #include "folderman.h" @@ -176,21 +177,11 @@ void SelectiveSyncTreeView::slotUpdateDirectories(QStringList list) pathToRemove.append('/'); // Check for excludes. - // - // We would like to use Folder::isFileExcluded, but the folder doesn't - // exist yet. So we just create one temporarily... - FolderDefinition def; - def.localPath = pathToRemove; - def.ignoreHiddenFiles = FolderMan::instance()->ignoreHiddenFiles(); - Folder f(def); QMutableListIterator it(list); while (it.hasNext()) { it.next(); - QString path = it.value(); - path.remove(pathToRemove); - if (f.isFileExcludedRelative(path)) { + if (ExcludedFiles::instance().isExcluded(it.value(), pathToRemove, FolderMan::instance()->ignoreHiddenFiles())) it.remove(); - } } // Since / cannot be in the blacklist, expand it to the actual diff --git a/src/libsync/accountfwd.h b/src/libsync/accountfwd.h index 28109f8171..8281998339 100644 --- a/src/libsync/accountfwd.h +++ b/src/libsync/accountfwd.h @@ -21,7 +21,7 @@ namespace OCC { class Account; typedef QSharedPointer AccountPtr; class AccountState; -typedef QSharedPointer AccountStatePtr; +typedef QExplicitlySharedDataPointer AccountStatePtr; } // namespace OCC diff --git a/src/libsync/excludedfiles.cpp b/src/libsync/excludedfiles.cpp index 4f84fc0b5b..3724c4880f 100644 --- a/src/libsync/excludedfiles.cpp +++ b/src/libsync/excludedfiles.cpp @@ -62,16 +62,20 @@ bool ExcludedFiles::reloadExcludes() return success; } -CSYNC_EXCLUDE_TYPE ExcludedFiles::isExcluded( - const QString& fullPath, - const QString& relativePath, +bool ExcludedFiles::isExcluded( + const QString& filePath, + const QString& basePath, bool excludeHidden) const { - QFileInfo fi(fullPath); + if (!filePath.startsWith(basePath)) { + // Mark paths we're not responsible for as excluded... + return true; + } + QFileInfo fi(filePath); if( excludeHidden ) { if( fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.')) ) { - return CSYNC_FILE_EXCLUDE_HIDDEN; + return true; } } @@ -79,6 +83,12 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::isExcluded( if (fi.isDir()) { type = CSYNC_FTW_TYPE_DIR; } + + QString relativePath = filePath.mid(basePath.size()); + if (relativePath.endsWith(QLatin1Char('/'))) { + relativePath.chop(1); + } + QReadLocker lock(&_mutex); - return csync_excluded_no_ctx(*_excludesPtr, relativePath.toUtf8(), type); + return csync_excluded_no_ctx(*_excludesPtr, relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED; } diff --git a/src/libsync/excludedfiles.h b/src/libsync/excludedfiles.h index da23c1bf06..3a71108b57 100644 --- a/src/libsync/excludedfiles.h +++ b/src/libsync/excludedfiles.h @@ -49,14 +49,12 @@ public: /** * Checks whether a file or directory should be excluded. * - * @param fullPath the absolute path to the file - * @param relativePath path relative to the folder - * - * For directories, the paths must not contain a trailing /. + * @param filePath the absolute path to the file + * @param basePath folder path from which to apply exclude rules */ - CSYNC_EXCLUDE_TYPE isExcluded( - const QString& fullPath, - const QString& relativePath, + bool isExcluded( + const QString& filePath, + const QString& basePath, bool excludeHidden) const; public slots: diff --git a/test/testfolderman.h b/test/testfolderman.h index c1273bac03..35ba8bab18 100644 --- a/test/testfolderman.h +++ b/test/testfolderman.h @@ -15,6 +15,7 @@ #include "utility.h" #include "folderman.h" +#include "account.h" #include "accountstate.h" using namespace OCC; @@ -52,10 +53,11 @@ private slots: f.write("hello"); } + AccountStatePtr newAccountState(new AccountState(Account::create())); FolderMan *folderman = FolderMan::instance(); QCOMPARE(folderman, &_fm); - QVERIFY(folderman->addFolder(0, folderDefinition(dir.path() + "/sub/ownCloud1"))); - QVERIFY(folderman->addFolder(0, folderDefinition(dir.path() + "/ownCloud2"))); + QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dir.path() + "/sub/ownCloud1"))); + QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dir.path() + "/ownCloud2"))); // those should be allowed From 2d2c7bc9b8cfe78cfdcf21fea722205135d6512c Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Wed, 9 Mar 2016 17:53:18 +0100 Subject: [PATCH 02/17] Move the SyncEngine construction to the Folder constructor The SyncEngine is now created only once, at construction of the Folder, instead of being reconstructed on each sync. --- src/gui/folder.cpp | 128 +++++++++++++++++-------------------- src/libsync/syncengine.cpp | 12 ++-- src/libsync/syncengine.h | 5 +- 3 files changed, 72 insertions(+), 73 deletions(-) diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index d94552b399..c0d0741cad 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -71,6 +71,9 @@ Folder::Folder(const FolderDefinition& definition, , _consecutiveFollowUpSyncs(0) , _journal(definition.localPath) { + qRegisterMetaType("SyncFileItemVector"); + qRegisterMetaType("SyncFileItem::Direction"); + qsrand(QTime::currentTime().msec()); _timeSinceLastSyncStart.start(); _timeSinceLastSyncDone.start(); @@ -85,14 +88,40 @@ Folder::Folder(const FolderDefinition& definition, checkLocalPath(); _syncResult.setFolder(_definition.alias); + + _engine.reset(new SyncEngine(_accountState->account(), path(), remoteUrl(), remotePath(), &_journal)); + // pass the setting if hidden files are to be ignored, will be read in csync_update + _engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles); + + if (!setIgnoredFiles()) + qWarning("Could not read system exclude file"); + + connect(_engine.data(), SIGNAL(rootEtag(QString)), this, SLOT(etagRetreivedFromSyncEngine(QString))); + connect(_engine.data(), SIGNAL(treeWalkResult(const SyncFileItemVector&)), + this, SLOT(slotThreadTreeWalkResult(const SyncFileItemVector&)), Qt::QueuedConnection); + connect(_engine.data(), SIGNAL(aboutToPropagate(SyncFileItemVector&)), + this, SLOT(slotAboutToPropagate(SyncFileItemVector&))); + + connect(_engine.data(), SIGNAL(started()), SLOT(slotSyncStarted()), Qt::QueuedConnection); + connect(_engine.data(), SIGNAL(finished(bool)), SLOT(slotSyncFinished(bool)), Qt::QueuedConnection); + connect(_engine.data(), SIGNAL(csyncError(QString)), SLOT(slotSyncError(QString)), Qt::QueuedConnection); + connect(_engine.data(), SIGNAL(csyncUnavailable()), SLOT(slotCsyncUnavailable()), Qt::QueuedConnection); + + //direct connection so the message box is blocking the sync. + connect(_engine.data(), SIGNAL(aboutToRemoveAllFiles(SyncFileItem::Direction,bool*)), + SLOT(slotAboutToRemoveAllFiles(SyncFileItem::Direction,bool*))); + connect(_engine.data(), SIGNAL(aboutToRestoreBackup(bool*)), + SLOT(slotAboutToRestoreBackup(bool*))); + connect(_engine.data(), SIGNAL(folderDiscovered(bool,QString)), this, SLOT(slotFolderDiscovered(bool,QString))); + connect(_engine.data(), SIGNAL(transmissionProgress(ProgressInfo)), this, SLOT(slotTransmissionProgress(ProgressInfo))); + connect(_engine.data(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)), + this, SLOT(slotItemCompleted(const SyncFileItem &, const PropagatorJob &))); + connect(_engine.data(), SIGNAL(syncItemDiscovered(const SyncFileItem &)), this, SLOT(slotSyncItemDiscovered(const SyncFileItem &))); + connect(_engine.data(), SIGNAL(newBigFolder(QString)), this, SLOT(slotNewBigFolderDiscovered(QString))); } Folder::~Folder() { - if( _engine ) { - _engine->abort(); - _engine.reset(0); - } } void Folder::checkLocalPath() @@ -183,7 +212,7 @@ QString Folder::cleanPath() bool Folder::isBusy() const { - return !_engine.isNull(); + return _engine->isSyncRunning(); } QString Folder::remotePath() const @@ -340,7 +369,7 @@ void Folder::bubbleUpSyncResult() SyncRunFileLog syncFileLog; - syncFileLog.start(path(), _engine ? _engine->stopWatch() : Utility::StopWatch() ); + syncFileLog.start(path(), _engine->isSyncRunning() ? _engine->stopWatch() : Utility::StopWatch() ); QElapsedTimer timer; timer.start(); @@ -538,7 +567,7 @@ void Folder::slotWatchedPathChanged(const QString& path) { // When no sync is running or it's in the prepare phase, we can // always schedule a new sync. - if (! _engine || _syncResult.status() == SyncResult::SyncPrepare) { + if (! _engine->isSyncRunning() || _syncResult.status() == SyncResult::SyncPrepare) { emit scheduleToSync(this); return; } @@ -614,7 +643,7 @@ bool Folder::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) return true; } // If sync is running, check _syncedItems, possibly give it STATUS_EVAL (=syncing down) - if (!_engine.isNull()) { + if (_engine->isSyncRunning()) { if (_engine->estimateState(fn, t, s)) { return true; } @@ -635,7 +664,7 @@ bool Folder::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) return true; } // If sync running: _syncedItems -> SyncingState - if (!_engine.isNull()) { + if (_engine->isSyncRunning()) { if (_engine->estimateState(fn, t, s)) { return true; } @@ -663,19 +692,19 @@ void Folder::removeFromSettings() const bool Folder::isFileExcludedAbsolute(const QString& fullPath) const { - return ExcludedFiles::instance().isExcluded(fullPath, path(), _definition.ignoreHiddenFiles); + return _engine->excludedFiles().isExcluded(fullPath, path(), _definition.ignoreHiddenFiles); } bool Folder::isFileExcludedRelative(const QString& relativePath) const { - return ExcludedFiles::instance().isExcluded(path() + relativePath, path(), _definition.ignoreHiddenFiles); + return _engine->excludedFiles().isExcluded(path() + relativePath, path(), _definition.ignoreHiddenFiles); } void Folder::watcherSlot(QString fn) { // FIXME: On OS X we could not do this "if" since on OS X the file watcher ignores events for ourselves // however to have the same behaviour atm on all platforms, we don't do it - if (!_engine.isNull()) { + if (_engine->isSyncRunning()) { qDebug() << Q_FUNC_INFO << "Sync running, IGNORE event for " << fn; return; } @@ -700,7 +729,7 @@ void Folder::slotTerminateSync() { qDebug() << "folder " << alias() << " Terminating!"; - if( _engine ) { + if( _engine->isSyncRunning() ) { _engine->abort(); // Do not display an error message, user knows his own actions. @@ -797,10 +826,6 @@ void Folder::startSync(const QStringList &pathList) qDebug() << "*** Start syncing " << remoteUrl().toString() << " - client version" << qPrintable(Theme::instance()->version()); - _engine.reset(new SyncEngine( _accountState->account(), path(), remoteUrl(), remotePath(), &_journal)); - // pass the setting if hidden files are to be ignored, will be read in csync_update - _engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles); - if (!setIgnoredFiles()) { slotSyncError(tr("Could not read system exclude file")); @@ -808,32 +833,6 @@ void Folder::startSync(const QStringList &pathList) return; } - qRegisterMetaType("SyncFileItemVector"); - qRegisterMetaType("SyncFileItem::Direction"); - - connect(_engine.data(), SIGNAL(rootEtag(QString)), this, SLOT(etagRetreivedFromSyncEngine(QString))); - connect( _engine.data(), SIGNAL(treeWalkResult(const SyncFileItemVector&)), - this, SLOT(slotThreadTreeWalkResult(const SyncFileItemVector&)), Qt::QueuedConnection); - connect( _engine.data(), SIGNAL(aboutToPropagate(SyncFileItemVector&)), - this, SLOT(slotAboutToPropagate(SyncFileItemVector&))); - - connect(_engine.data(), SIGNAL(started()), SLOT(slotSyncStarted()), Qt::QueuedConnection); - connect(_engine.data(), SIGNAL(finished(bool)), SLOT(slotSyncFinished(bool)), Qt::QueuedConnection); - connect(_engine.data(), SIGNAL(csyncError(QString)), SLOT(slotSyncError(QString)), Qt::QueuedConnection); - connect(_engine.data(), SIGNAL(csyncUnavailable()), SLOT(slotCsyncUnavailable()), Qt::QueuedConnection); - - //direct connection so the message box is blocking the sync. - connect(_engine.data(), SIGNAL(aboutToRemoveAllFiles(SyncFileItem::Direction,bool*)), - SLOT(slotAboutToRemoveAllFiles(SyncFileItem::Direction,bool*))); - connect(_engine.data(), SIGNAL(aboutToRestoreBackup(bool*)), - SLOT(slotAboutToRestoreBackup(bool*))); - connect(_engine.data(), SIGNAL(folderDiscovered(bool,QString)), this, SLOT(slotFolderDiscovered(bool,QString))); - connect(_engine.data(), SIGNAL(transmissionProgress(ProgressInfo)), this, SLOT(slotTransmissionProgress(ProgressInfo))); - connect(_engine.data(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)), - this, SLOT(slotItemCompleted(const SyncFileItem &, const PropagatorJob &))); - connect(_engine.data(), SIGNAL(syncItemDiscovered(const SyncFileItem &)), this, SLOT(slotSyncItemDiscovered(const SyncFileItem &))); - connect(_engine.data(), SIGNAL(newBigFolder(QString)), this, SLOT(slotNewBigFolderDiscovered(QString))); - setDirtyNetworkLimits(); ConfigFile cfgFile; @@ -850,27 +849,24 @@ void Folder::startSync(const QStringList &pathList) void Folder::setDirtyNetworkLimits() { - if (_engine) { - - ConfigFile cfg; - int downloadLimit = -75; // 75% - int useDownLimit = cfg.useDownloadLimit(); - if (useDownLimit >= 1) { - downloadLimit = cfg.downloadLimit() * 1000; - } else if (useDownLimit == 0) { - downloadLimit = 0; - } - - int uploadLimit = -75; // 75% - int useUpLimit = cfg.useUploadLimit(); - if ( useUpLimit >= 1) { - uploadLimit = cfg.uploadLimit() * 1000; - } else if (useUpLimit == 0) { - uploadLimit = 0; - } - - _engine->setNetworkLimits(uploadLimit, downloadLimit); + ConfigFile cfg; + int downloadLimit = -75; // 75% + int useDownLimit = cfg.useDownloadLimit(); + if (useDownLimit >= 1) { + downloadLimit = cfg.downloadLimit() * 1000; + } else if (useDownLimit == 0) { + downloadLimit = 0; } + + int uploadLimit = -75; // 75% + int useUpLimit = cfg.useUploadLimit(); + if ( useUpLimit >= 1) { + uploadLimit = cfg.uploadLimit() * 1000; + } else if (useUpLimit == 0) { + uploadLimit = 0; + } + + _engine->setNetworkLimits(uploadLimit, downloadLimit); } @@ -909,11 +905,7 @@ void Folder::slotSyncFinished(bool success) } bubbleUpSyncResult(); - bool anotherSyncNeeded = false; - if (_engine) { - anotherSyncNeeded = _engine->isAnotherSyncNeeded(); - _engine.reset(0); - } + bool anotherSyncNeeded = _engine->isAnotherSyncNeeded(); // _watcher->setEventsEnabledDelayed(2000); diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 7eb50550e4..6e238cfa13 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -52,7 +52,7 @@ extern "C" const char *csync_instruction_str(enum csync_instructions_e instr); namespace OCC { -bool SyncEngine::_syncRunning = false; +bool SyncEngine::s_anySyncRunning = false; qint64 SyncEngine::minimumFileAgeForUpload = 2000; @@ -60,6 +60,7 @@ SyncEngine::SyncEngine(AccountPtr account, const QString& localPath, const QUrl& remoteURL, const QString& remotePath, OCC::SyncJournalDb* journal) : _account(account) , _needsUpdate(false) + , _syncRunning(false) , _localPath(localPath) , _remoteUrl(remoteURL) , _remotePath(remotePath) @@ -94,11 +95,11 @@ SyncEngine::SyncEngine(AccountPtr account, const QString& localPath, _excludedFiles.reset(new ExcludedFiles(&_csync_ctx->excludes)); _thread.setObjectName("SyncEngine_Thread"); - _thread.start(); } SyncEngine::~SyncEngine() { + abort(); _excludedFiles.reset(); csync_destroy(_csync_ctx); _thread.quit(); @@ -662,10 +663,11 @@ void SyncEngine::startSync() } } + Q_ASSERT(!s_anySyncRunning); Q_ASSERT(!_syncRunning); + s_anySyncRunning = true; _syncRunning = true; - - Q_ASSERT(_csync_ctx); + _anotherSyncNeeded = false; if (!QDir(_localPath).exists()) { // No _tr, it should only occur in non-mirall @@ -741,6 +743,7 @@ void SyncEngine::startSync() qDebug() << "#### Discovery start #################################################### >>"; + _thread.start(); _discoveryMainThread = new DiscoveryMainThread(account()); _discoveryMainThread->setParent(this); connect(this, SIGNAL(finished(bool)), _discoveryMainThread, SLOT(deleteLater())); @@ -1000,6 +1003,7 @@ void SyncEngine::finalize(bool success) qDebug() << "CSync run took " << _stopWatch.addLapTime(QLatin1String("Sync Finished")); _stopWatch.stop(); + s_anySyncRunning = false; _syncRunning = false; emit finished(success); diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index 7d24e89621..b35cbe31fa 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -69,6 +69,8 @@ public: /* Abort the sync. Called from the main thread */ void abort(); + bool isSyncRunning() const { return _syncRunning; } + /* Set the maximum size a folder can have without asking for confirmation * -1 means infinite */ @@ -168,7 +170,7 @@ private: // cleanup and emit the finished signal void finalize(bool success); - static bool _syncRunning; //true when one sync is running somewhere (for debugging) + static bool s_anySyncRunning; //true when one sync is running somewhere (for debugging) // Must only be acessed during update and reconcile QMap _syncItemMap; @@ -180,6 +182,7 @@ private: AccountPtr _account; CSYNC *_csync_ctx; bool _needsUpdate; + bool _syncRunning; QString _localPath; QUrl _remoteUrl; QString _remotePath; From a4e0899af4eec5883bb16011c5760ca0c3707f76 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Fri, 18 Mar 2016 17:15:30 +0100 Subject: [PATCH 03/17] Remove the mutex from ExcludedFiles It's always accessed from the main thread. --- src/libsync/excludedfiles.cpp | 5 ----- src/libsync/excludedfiles.h | 2 -- 2 files changed, 7 deletions(-) diff --git a/src/libsync/excludedfiles.cpp b/src/libsync/excludedfiles.cpp index 3724c4880f..d4eed06d86 100644 --- a/src/libsync/excludedfiles.cpp +++ b/src/libsync/excludedfiles.cpp @@ -14,8 +14,6 @@ #include "excludedfiles.h" #include -#include -#include extern "C" { #include "std/c_string.h" @@ -44,13 +42,11 @@ ExcludedFiles& ExcludedFiles::instance() void ExcludedFiles::addExcludeFilePath(const QString& path) { - QWriteLocker locker(&_mutex); _excludeFiles.append(path); } bool ExcludedFiles::reloadExcludes() { - QWriteLocker locker(&_mutex); c_strlist_destroy(*_excludesPtr); *_excludesPtr = NULL; @@ -89,6 +85,5 @@ bool ExcludedFiles::isExcluded( relativePath.chop(1); } - QReadLocker lock(&_mutex); return csync_excluded_no_ctx(*_excludesPtr, relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED; } diff --git a/src/libsync/excludedfiles.h b/src/libsync/excludedfiles.h index 3a71108b57..5e47f7c2d8 100644 --- a/src/libsync/excludedfiles.h +++ b/src/libsync/excludedfiles.h @@ -16,7 +16,6 @@ #include "owncloudlib.h" #include -#include #include extern "C" { @@ -68,7 +67,6 @@ private: // but the pointer can be in a csync_context so that it can itself also query the list. c_strlist_t** _excludesPtr; QStringList _excludeFiles; - mutable QReadWriteLock _mutex; }; } // namespace OCC From c090a511fd07e573edee604f15eca5c53b83a9c5 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Thu, 17 Mar 2016 12:15:47 +0100 Subject: [PATCH 04/17] Remove OwnCloud6 specific sharing code If users encounter this situation, the share icon will simply not show. This simplifies the transition to move this code in libsync. --- src/gui/socketapi.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 051041b18f..83c04b4776 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -679,17 +679,9 @@ SyncFileStatus SocketApi::fileStatus(Folder *folder, const QString& systemFileNa } } - if (rec.isValid()) { - if (rec._remotePerm.isNull()) { - // probably owncloud 6, that does not have permissions flag yet. - QString url = folder->remoteUrl().toString() + fileName; - if (url.contains( folder->accountState()->account()->davPath() + QLatin1String("Shared/") )) { - status.setSharedWithMe(true); - } - } else if (rec._remotePerm.contains("S")) { - status.setSharedWithMe(true); - } - } + if (rec.isValid() && rec._remotePerm.contains("S")) + status.setSharedWithMe(true); + if (status.tag() == SyncFileStatus::STATUS_NEW) { // check the parent folder if it is shared and if it is allowed to create a file/dir within QDir d( fi.path() ); From 6d3fe9d865d5f0f4c4e2d03f289762ecbb521f68 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Thu, 17 Mar 2016 19:38:44 +0100 Subject: [PATCH 05/17] Remove the tainted folder logic This currently is no-op code since the socket API isn't notified that the tainted folder list changed, and the result is the same since a sync will be triggered within the next 5 seconds and the modified folder will be shown as SYNC at that point anyway. Removing the dependency to the file watcher allows moving the status estimation logic to libsync. --- src/gui/folder.cpp | 38 -------------------------------------- src/gui/folder.h | 1 - src/gui/folderman.cpp | 3 --- 3 files changed, 42 deletions(-) diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index c0d0741cad..272d5445ff 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -626,10 +626,6 @@ void Folder::slotThreadTreeWalkResult(const SyncFileItemVector& items) void Folder::slotAboutToPropagate(SyncFileItemVector& items) { - // empty the tainted list since the status generation code will use the _syncedItems - // (which imply the folder) to generate the syncing state icon now. - _stateTaintedFolders.clear(); - addErroredSyncItemPathsToList(items, &this->_stateLastSyncItemsWithErrorNew); } @@ -648,14 +644,6 @@ bool Folder::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) return true; } } - if(!fn.endsWith(QLatin1Char('/'))) { - fn.append(QLatin1Char('/')); - } - if (Utility::doesSetContainPrefix(_stateTaintedFolders, fn)) { - qDebug() << Q_FUNC_INFO << "Folder is tainted, EVAL!" << fn; - s->set(SyncFileStatus::STATUS_EVAL); - return true; - } return false; } else if ( t== CSYNC_FTW_TYPE_FILE) { // check if errorList has the directory/file @@ -700,31 +688,6 @@ bool Folder::isFileExcludedRelative(const QString& relativePath) const return _engine->excludedFiles().isExcluded(path() + relativePath, path(), _definition.ignoreHiddenFiles); } -void Folder::watcherSlot(QString fn) -{ - // FIXME: On OS X we could not do this "if" since on OS X the file watcher ignores events for ourselves - // however to have the same behaviour atm on all platforms, we don't do it - if (_engine->isSyncRunning()) { - qDebug() << Q_FUNC_INFO << "Sync running, IGNORE event for " << fn; - return; - } - QFileInfo fi(fn); - if (fi.isFile()) { - fn = fi.path(); // depending on OS, file watcher might be for dir or file - } - // Make it a relative path depending on the folder - QString relativePath = fn.remove(0, path().length()); - if( !relativePath.endsWith(QLatin1Char('/'))) { - relativePath.append(QLatin1Char('/')); - } - qDebug() << Q_FUNC_INFO << fi.canonicalFilePath() << fn << relativePath; - _stateTaintedFolders.insert(relativePath); - - // Notify the SocketAPI? -} - - - void Folder::slotTerminateSync() { qDebug() << "folder " << alias() << " Terminating!"; @@ -913,7 +876,6 @@ void Folder::slotSyncFinished(bool success) // This is for sync state calculation _stateLastSyncItemsWithError = _stateLastSyncItemsWithErrorNew; _stateLastSyncItemsWithErrorNew.clear(); - _stateTaintedFolders.clear(); // heuristic: assume the sync had been done, new file watches needed to taint dirs if (_csyncError) { _syncResult.setStatus(SyncResult::Error); diff --git a/src/gui/folder.h b/src/gui/folder.h index 8a7976ac71..59fa7f61fb 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -267,7 +267,6 @@ private slots: void slotEmitFinishedDelayed(); - void watcherSlot(QString); void slotNewBigFolderDiscovered(const QString &); private: diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 4215ae0c54..69d028cda1 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -147,9 +147,6 @@ void FolderMan::registerFolderMonitor( Folder *folder ) // is lost this way, but we do not need it for the current implementation. connect(fw, SIGNAL(pathChanged(QString)), folder, SLOT(slotWatchedPathChanged(QString))); _folderWatchers.insert(folder->alias(), fw); - - // This is at the moment only for the behaviour of the SocketApi. - connect(fw, SIGNAL(pathChanged(QString)), folder, SLOT(watcherSlot(QString))); } // register the folder with the socket API From 6e16e34799d45fad4790d7bb5534322337fc8fb0 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Thu, 17 Mar 2016 20:58:51 +0100 Subject: [PATCH 06/17] Remove SocketApi::dbFileRecord_capi in favor of JournalDB::getFileRecord --- src/gui/socketapi.cpp | 103 ++---------------------------------------- src/gui/socketapi.h | 5 -- 2 files changed, 5 insertions(+), 103 deletions(-) diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 83c04b4776..39dc40f88c 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -195,24 +195,8 @@ void SocketApi::slotRegisterPath( const QString& alias ) void SocketApi::slotUnregisterPath( const QString& alias ) { Folder *f = FolderMan::instance()->folder(alias); - if (f) { + if (f) broadcastMessage(QLatin1String("UNREGISTER_PATH"), f->path(), QString::null, true ); - - if( _dbQueries.contains(f)) { - auto h = _dbQueries[f]; - if( h ) { - h->finish(); - } - _dbQueries.remove(f); - } - if( _openDbs.contains(f) ) { - auto db = _openDbs[f]; - if( db ) { - db->close(); - } - _openDbs.remove(f); - } - } } void SocketApi::slotUpdateFolderView(Folder *f) @@ -402,7 +386,7 @@ void SocketApi::command_SHARE(const QString& localFile, QIODevice* socket) return; } - SyncJournalFileRecord rec = dbFileRecord_capi(shareFolder, localFileClean); + SyncJournalFileRecord rec = shareFolder->journalDb()->getFileRecord(localFileClean); bool allowReshare = true; // lets assume the good if( rec.isValid() ) { @@ -494,83 +478,6 @@ QString SocketApi::buildRegisterPathMessage(const QString& path) return message; } -SqlQuery* SocketApi::getSqlQuery( Folder *folder ) -{ - if( !folder ) { - return 0; - } - - if( _dbQueries.contains(folder) ) { - return _dbQueries[folder].data(); - } - - /* No valid sql query object yet for this folder */ - int rc; - const QString sql("SELECT inode, modtime, type, md5, fileid, remotePerm FROM " - "metadata WHERE phash=?1"); - QString dbFileName = folder->journalDb()->databaseFilePath(); - - QFileInfo fi(dbFileName); - if( fi.exists() ) { - auto db = QSharedPointer::create(); - - if( db && db->openReadOnly(dbFileName) ) { - _openDbs.insert(folder, db); - - QSharedPointer query(new SqlQuery(*db)); - rc = query->prepare(sql); - - if( rc != SQLITE_OK ) { - qDebug() << "Unable to prepare the query statement:" << rc; - return 0; // do not insert into hash - } - _dbQueries.insert( folder, query); - return query.data(); - } else { - qDebug() << "Unable to open db" << dbFileName; - } - } else { - qDebug() << Q_FUNC_INFO << "Journal to query does not yet exist."; - } - return 0; -} - -SyncJournalFileRecord SocketApi::dbFileRecord_capi( Folder *folder, QString fileName ) -{ - if( !(folder && folder->journalDb()) ) { - return SyncJournalFileRecord(); - } - - if( fileName.startsWith( folder->path() )) { - fileName.remove(0, folder->path().length()); - } - - // remove trailing slash - if( fileName.endsWith( QLatin1Char('/') ) ) { - fileName.truncate(fileName.length()-1); - } - SqlQuery *query = getSqlQuery(folder); - SyncJournalFileRecord rec; - - if( query ) { - qlonglong phash = SyncJournalDb::getPHash( fileName ); - query->bindValue(1, phash); - // int column_count = sqlite3_column_count(stmt); - - if (query->next()) { - rec._path = fileName; - rec._inode = query->int64Value(0); - rec._modtime = Utility::qDateTimeFromTime_t( query->int64Value(1)); - rec._type = query->intValue(2); - rec._etag = query->baValue(3); - rec._fileId = query->baValue(4); - rec._remotePerm = query->baValue(5); - } - query->reset_and_clear_bindings(); - } - return rec; -} - /** * Get status about a single file. */ @@ -626,7 +533,7 @@ SyncFileStatus SocketApi::fileStatus(Folder *folder, const QString& systemFileNa } SyncFileStatus status(SyncFileStatus::STATUS_NONE); - SyncJournalFileRecord rec = dbFileRecord_capi(folder, fileName ); + SyncJournalFileRecord rec = folder->journalDb()->getFileRecord(fileName); if (folder->estimateState(fileName, type, &status)) { qDebug() << "Folder estimated status for" << fileName << "to" << status.toSocketAPIString(); @@ -686,14 +593,14 @@ SyncFileStatus SocketApi::fileStatus(Folder *folder, const QString& systemFileNa // check the parent folder if it is shared and if it is allowed to create a file/dir within QDir d( fi.path() ); auto parentPath = d.path(); - auto dirRec = dbFileRecord_capi(folder, parentPath); + auto dirRec = folder->journalDb()->getFileRecord(parentPath); bool isDir = type == CSYNC_FTW_TYPE_DIR; while( !d.isRoot() && !(d.exists() && dirRec.isValid()) ) { d.cdUp(); // returns true if the dir exists. parentPath = d.path(); // cut the folder path - dirRec = dbFileRecord_capi(folder, parentPath); + dirRec = folder->journalDb()->getFileRecord(parentPath); isDir = true; } diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index f17660e4a5..6e69c2e2c9 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -17,7 +17,6 @@ #define SOCKETAPI_H #include "syncfileitem.h" -#include "syncjournalfilerecord.h" #include "ownsql.h" #if defined(Q_OS_MAC) @@ -66,8 +65,6 @@ private slots: private: SyncFileStatus fileStatus(Folder *folder, const QString& systemFileName); - SyncJournalFileRecord dbFileRecord_capi( Folder *folder, QString fileName ); - SqlQuery *getSqlQuery( Folder *folder ); void sendMessage(QIODevice* socket, const QString& message, bool doWait = false); void broadcastMessage(const QString& verb, const QString &path, const QString &status = QString::null, bool doWait = false); @@ -84,8 +81,6 @@ private: QList _listeners; SocketApiServer _localServer; - QHash> _dbQueries; - QHash> _openDbs; }; } From dac4bd8370bb38c6a37aaa99292c681c9879b259 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Fri, 18 Mar 2016 18:31:01 +0100 Subject: [PATCH 07/17] Remove null-checks for FolderMan::_socketApi It's now created in the constructor and won't be null. --- src/gui/folderman.cpp | 41 +++++++++++++++-------------------------- src/gui/folderman.h | 3 +-- src/gui/socketapi.h | 2 +- 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 69d028cda1..661d0f3ce2 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -32,7 +32,6 @@ #endif #include -#include #include #include #include @@ -50,7 +49,7 @@ FolderMan::FolderMan(QObject *parent) : Q_ASSERT(!_instance); _instance = this; - _socketApi = new SocketApi(this); + _socketApi.reset(new SocketApi); ConfigFile cfg; int polltime = cfg.remotePollInterval(); @@ -89,9 +88,7 @@ void FolderMan::unloadFolder( Folder *f ) return; } - if( _socketApi ) { - _socketApi->slotUnregisterPath(f->alias()); - } + _socketApi->slotUnregisterPath(f->alias()); if( _folderWatchers.contains(f->alias())) { _folderWatchers.remove(f->alias()); @@ -150,9 +147,7 @@ void FolderMan::registerFolderMonitor( Folder *folder ) } // register the folder with the socket API - if( _socketApi ) { - _socketApi->slotRegisterPath(folder->alias()); - } + _socketApi->slotRegisterPath(folder->alias()); } void FolderMan::addMonitorPath( const QString& alias, const QString& path ) @@ -303,7 +298,7 @@ QString FolderMan::escapeAlias( const QString& alias ) SocketApi *FolderMan::socketApi() { - return this->_socketApi; + return this->_socketApi.data(); } QString FolderMan::unescapeAlias( const QString& alias ) @@ -472,21 +467,17 @@ void FolderMan::slotScheduleSync( Folder *f ) } auto alias = f->alias(); - if( _socketApi ) { - // We want the SocketAPI to already now update so that it can show the EVAL icon - // for files/folders. Only do this when not syncing, else we might get a lot - // of those notifications. - _socketApi->slotUpdateFolderView(f); - } + // We want the SocketAPI to already now update so that it can show the EVAL icon + // for files/folders. Only do this when not syncing, else we might get a lot + // of those notifications. + _socketApi->slotUpdateFolderView(f); qDebug() << "Schedule folder " << alias << " to sync!"; if( ! _scheduleQueue.contains(f) ) { if( !f->canSync() ) { qDebug() << "Folder is not ready to sync, not scheduled!"; - if( _socketApi ) { - _socketApi->slotUpdateFolderView(f); - } + _socketApi->slotUpdateFolderView(f); return; } f->prepareToSync(); @@ -928,15 +919,13 @@ bool FolderMan::startFromScratch( const QString& localFolder ) } // Disconnect the socket api from the database to avoid that locking of the // db file does not allow to move this dir. - if( _socketApi ) { - Folder *f = folderForPath(localFolder); - if(f) { - if( localFolder.startsWith(f->path()) ) { - _socketApi->slotUnregisterPath(f->alias()); - } - f->journalDb()->close(); - f->slotTerminateSync(); // Normally it should not be running, but viel hilft viel + Folder *f = folderForPath(localFolder); + if(f) { + if( localFolder.startsWith(f->path()) ) { + _socketApi->slotUnregisterPath(f->alias()); } + f->journalDb()->close(); + f->slotTerminateSync(); // Normally it should not be running, but viel hilft viel } // Make a backup of the folder/file. diff --git a/src/gui/folderman.h b/src/gui/folderman.h index 59b467acf1..124863f2b4 100644 --- a/src/gui/folderman.h +++ b/src/gui/folderman.h @@ -19,7 +19,6 @@ #include #include #include -#include #include "folder.h" #include "folderwatcher.h" @@ -228,7 +227,7 @@ private: QPointer _currentEtagJob; // alias of Folder running the current RequestEtagJob QMap _folderWatchers; - QPointer _socketApi; + QScopedPointer _socketApi; /** The aliases of folders that shall be synced. */ QQueue _scheduleQueue; diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index 6e69c2e2c9..5e29def36e 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -44,7 +44,7 @@ class SocketApi : public QObject Q_OBJECT public: - SocketApi(QObject* parent); + SocketApi(QObject* parent = 0); virtual ~SocketApi(); public slots: From da7b9916e52eefcc8f5ea7501213899d076fcfbe Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Thu, 17 Mar 2016 12:26:44 +0100 Subject: [PATCH 08/17] Move the SocketApi business logic to a libsync SyncFileStatusTracker class This will allow testing this code and avoid going through too many layers to get notified and a file status changed. --- src/gui/accountsettings.cpp | 1 + src/gui/folder.cpp | 70 -------- src/gui/folder.h | 20 +-- src/gui/socketapi.cpp | 149 +---------------- src/libsync/CMakeLists.txt | 1 + src/libsync/syncengine.cpp | 1 + src/libsync/syncengine.h | 7 +- src/libsync/syncfilestatustracker.cpp | 230 ++++++++++++++++++++++++++ src/libsync/syncfilestatustracker.h | 51 ++++++ 9 files changed, 296 insertions(+), 234 deletions(-) create mode 100644 src/libsync/syncfilestatustracker.cpp create mode 100644 src/libsync/syncfilestatustracker.h diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 383751bc30..83756bf8b0 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -34,6 +34,7 @@ #include #include +#include #include #include #include diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 272d5445ff..9cc7aef0e6 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -99,8 +99,6 @@ Folder::Folder(const FolderDefinition& definition, connect(_engine.data(), SIGNAL(rootEtag(QString)), this, SLOT(etagRetreivedFromSyncEngine(QString))); connect(_engine.data(), SIGNAL(treeWalkResult(const SyncFileItemVector&)), this, SLOT(slotThreadTreeWalkResult(const SyncFileItemVector&)), Qt::QueuedConnection); - connect(_engine.data(), SIGNAL(aboutToPropagate(SyncFileItemVector&)), - this, SLOT(slotAboutToPropagate(SyncFileItemVector&))); connect(_engine.data(), SIGNAL(started()), SLOT(slotSyncStarted()), Qt::QueuedConnection); connect(_engine.data(), SIGNAL(finished(bool)), SLOT(slotSyncFinished(bool)), Qt::QueuedConnection); @@ -595,71 +593,11 @@ void Folder::slotWatchedPathChanged(const QString& path) } } -/** - * Whether this item should get an ERROR icon through the Socket API. - * - * The Socket API should only present serious, permanent errors to the user. - * In particular SoftErrors should just retain their 'needs to be synced' - * icon as the problem is most likely going to resolve itself quickly and - * automatically. - */ -static bool showErrorInSocketApi(const SyncFileItem& item) -{ - const auto status = item._status; - return status == SyncFileItem::NormalError - || status == SyncFileItem::FatalError; -} - -static void addErroredSyncItemPathsToList(const SyncFileItemVector& items, QSet* set) { - foreach (const SyncFileItemPtr &item, items) { - if (showErrorInSocketApi(*item)) { - set->insert(item->_file); - } - } -} - void Folder::slotThreadTreeWalkResult(const SyncFileItemVector& items) { - addErroredSyncItemPathsToList(items, &this->_stateLastSyncItemsWithErrorNew); _syncResult.setSyncFileItemVector(items); } -void Folder::slotAboutToPropagate(SyncFileItemVector& items) -{ - addErroredSyncItemPathsToList(items, &this->_stateLastSyncItemsWithErrorNew); -} - - -bool Folder::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) -{ - if (t == CSYNC_FTW_TYPE_DIR) { - if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) { - qDebug() << Q_FUNC_INFO << "Folder has error" << fn; - s->set(SyncFileStatus::STATUS_ERROR); - return true; - } - // If sync is running, check _syncedItems, possibly give it STATUS_EVAL (=syncing down) - if (_engine->isSyncRunning()) { - if (_engine->estimateState(fn, t, s)) { - return true; - } - } - return false; - } else if ( t== CSYNC_FTW_TYPE_FILE) { - // check if errorList has the directory/file - if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) { - s->set(SyncFileStatus::STATUS_ERROR); - return true; - } - // If sync running: _syncedItems -> SyncingState - if (_engine->isSyncRunning()) { - if (_engine->estimateState(fn, t, s)) { - return true; - } - } - } - return false; -} void Folder::saveToSettings() const { @@ -873,10 +811,6 @@ void Folder::slotSyncFinished(bool success) - // This is for sync state calculation - _stateLastSyncItemsWithError = _stateLastSyncItemsWithErrorNew; - _stateLastSyncItemsWithErrorNew.clear(); - if (_csyncError) { _syncResult.setStatus(SyncResult::Error); qDebug() << " ** error Strings: " << _errors; @@ -972,10 +906,6 @@ void Folder::slotTransmissionProgress(const ProgressInfo &pi) // a item is completed: count the errors and forward to the ProgressDispatcher void Folder::slotItemCompleted(const SyncFileItem &item, const PropagatorJob& job) { - if (showErrorInSocketApi(item)) { - _stateLastSyncItemsWithErrorNew.insert(item._file); - } - if (Progress::isWarningKind(item._status)) { // Count all error conditions. _syncResult.setWarnCount(_syncResult.warnCount()+1); diff --git a/src/gui/folder.h b/src/gui/folder.h index 59fa7f61fb..a76b9e2557 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -26,16 +26,9 @@ #include -#include -#include -#include #include #include -#include -#include -#include - class QThread; class QSettings; @@ -181,8 +174,7 @@ public: // Used by the Socket API SyncJournalDb *journalDb() { return &_journal; } - - bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s); + SyncEngine &syncEngine() { return *_engine; } RequestEtagJob *etagJob() { return _requestEtagJob; } qint64 msecSinceLastSync() const { return _timeSinceLastSyncDone.elapsed(); } @@ -262,7 +254,6 @@ private slots: void etagRetreived(const QString &); void etagRetreivedFromSyncEngine(const QString &); - void slotAboutToPropagate(SyncFileItemVector& ); void slotThreadTreeWalkResult(const SyncFileItemVector& ); // after sync is done void slotEmitFinishedDelayed(); @@ -304,15 +295,6 @@ private: /// Reset when no follow-up is requested. int _consecutiveFollowUpSyncs; - // SocketAPI: Cache files and folders that had errors so that they can - // get a red ERROR icon. - QSet _stateLastSyncItemsWithErrorNew; // gets moved to _stateLastSyncItemsWithError at end of sync - QSet _stateLastSyncItemsWithError; - - // SocketAPI: A folder is tained if we got a file watcher notification - // for it. It's displayed as EVAL. - QSet _stateTaintedFolders; - SyncJournalDb _journal; ClientProxy _clientProxy; diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 39dc40f88c..b3c7161f6c 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -23,6 +23,7 @@ #include "utility.h" #include "theme.h" #include "syncjournalfilerecord.h" +#include "syncengine.h" #include "syncfileitem.h" #include "filesystem.h" #include "version.h" @@ -215,7 +216,7 @@ void SocketApi::slotUpdateFolderView(Folder *f) f->syncResult().status() == SyncResult::SetupError ) { broadcastMessage(QLatin1String("STATUS"), f->path() , - this->fileStatus(f, "").toSocketAPIString()); + f->syncEngine().syncFileStatusTracker().fileStatus("").toSocketAPIString()); broadcastMessage(QLatin1String("UPDATE_VIEW"), f->path() ); } else { @@ -235,7 +236,7 @@ void SocketApi::slotItemCompleted(const QString &folder, const SyncFileItem &ite return; } - auto status = this->fileStatus(f, item.destination()); + auto status = f->syncEngine().syncFileStatusTracker().fileStatus(item.destination()); const QString path = f->path() + item.destination(); broadcastMessage(QLatin1String("STATUS"), path, status.toSocketAPIString()); } @@ -330,7 +331,7 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QIODevice* statusString = QLatin1String("NOP"); } else { const QString file = QDir::cleanPath(argument).mid(syncFolder->cleanPath().length()+1); - SyncFileStatus fileStatus = this->fileStatus(syncFolder, file); + SyncFileStatus fileStatus = syncFolder->syncEngine().syncFileStatusTracker().fileStatus(file); statusString = fileStatus.toSocketAPIString(); } @@ -367,7 +368,7 @@ void SocketApi::command_SHARE(const QString& localFile, QIODevice* socket) } else { const QString localFileClean = QDir::cleanPath(localFile); const QString file = localFileClean.mid(shareFolder->cleanPath().length()+1); - SyncFileStatus fileStatus = this->fileStatus(shareFolder, file); + SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file); // Verify the file is on the server (to our knowledge of course) if (fileStatus.tag() != SyncFileStatus::STATUS_UPTODATE && @@ -423,7 +424,7 @@ void SocketApi::command_SHARE_STATUS(const QString &localFile, QIODevice *socket sendMessage(socket, message); } else { const QString file = QDir::cleanPath(localFile).mid(shareFolder->cleanPath().length()+1); - SyncFileStatus fileStatus = this->fileStatus(shareFolder, file); + SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file); // Verify the file is on the server (to our knowledge of course) if (fileStatus.tag() != SyncFileStatus::STATUS_UPTODATE && @@ -478,142 +479,4 @@ QString SocketApi::buildRegisterPathMessage(const QString& path) return message; } -/** - * Get status about a single file. - */ -SyncFileStatus SocketApi::fileStatus(Folder *folder, const QString& systemFileName) -{ - QString file = folder->path(); - QString fileName = systemFileName.normalized(QString::NormalizationForm_C); - QString fileNameSlash = fileName; - - if(fileName != QLatin1String("/") && !fileName.isEmpty()) { - file += fileName; - } - - if( fileName.endsWith(QLatin1Char('/')) ) { - fileName.truncate(fileName.length()-1); - qDebug() << "Removed trailing slash: " << fileName; - } else { - fileNameSlash += QLatin1Char('/'); - } - - const QFileInfo fi(file); - if( !FileSystem::fileExists(file, fi) ) { - qDebug() << "OO File " << file << " is not existing"; - return SyncFileStatus(SyncFileStatus::STATUS_STAT_ERROR); - } - - // file is ignored? - // Qt considers .lnk files symlinks on Windows so we need to work - // around that here. - if( fi.isSymLink() -#ifdef Q_OS_WIN - && fi.suffix() != "lnk" -#endif - ) { - return SyncFileStatus(SyncFileStatus::STATUS_IGNORE); - } - - csync_ftw_type_e type = CSYNC_FTW_TYPE_FILE; - if( fi.isDir() ) { - type = CSYNC_FTW_TYPE_DIR; - } - - // Is it excluded? - if( folder->isFileExcludedRelative(fileName) ) { - return SyncFileStatus(SyncFileStatus::STATUS_IGNORE); - } - - // Error if it is in the selective sync blacklist - foreach(const auto &s, folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList)) { - if (fileNameSlash.startsWith(s)) { - return SyncFileStatus(SyncFileStatus::STATUS_ERROR); - } - } - - SyncFileStatus status(SyncFileStatus::STATUS_NONE); - SyncJournalFileRecord rec = folder->journalDb()->getFileRecord(fileName); - - if (folder->estimateState(fileName, type, &status)) { - qDebug() << "Folder estimated status for" << fileName << "to" << status.toSocketAPIString(); - } else if (fileName == "") { - // sync folder itself - switch (folder->syncResult().status()) { - case SyncResult::Undefined: - case SyncResult::NotYetStarted: - case SyncResult::SyncPrepare: - case SyncResult::SyncRunning: - status.set(SyncFileStatus::STATUS_EVAL); - return status; - - case SyncResult::Success: - case SyncResult::Problem: - status.set(SyncFileStatus::STATUS_UPTODATE); - return status; - - case SyncResult::Error: - case SyncResult::SetupError: - case SyncResult::SyncAbortRequested: - status.set(SyncFileStatus::STATUS_ERROR); - return status; - - case SyncResult::Paused: - status.set(SyncFileStatus::STATUS_IGNORE); - return status; - } - } else if (type == CSYNC_FTW_TYPE_DIR) { - if (rec.isValid()) { - status.set(SyncFileStatus::STATUS_UPTODATE); - } else { - qDebug() << "Could not determine state for folder" << fileName << "will set STATUS_NEW"; - status.set(SyncFileStatus::STATUS_NEW); - } - } else if (type == CSYNC_FTW_TYPE_FILE) { - if (rec.isValid()) { - if( FileSystem::getModTime(fi.absoluteFilePath()) == Utility::qDateTimeToTime_t(rec._modtime) ) { - status.set(SyncFileStatus::STATUS_UPTODATE); - } else { - if (rec._remotePerm.isNull() || rec._remotePerm.contains("W") ) { - status.set(SyncFileStatus::STATUS_EVAL); - } else { - status.set(SyncFileStatus::STATUS_ERROR); - } - } - } else { - qDebug() << "Could not determine state for file" << fileName << "will set STATUS_NEW"; - status.set(SyncFileStatus::STATUS_NEW); - } - } - - if (rec.isValid() && rec._remotePerm.contains("S")) - status.setSharedWithMe(true); - - if (status.tag() == SyncFileStatus::STATUS_NEW) { - // check the parent folder if it is shared and if it is allowed to create a file/dir within - QDir d( fi.path() ); - auto parentPath = d.path(); - auto dirRec = folder->journalDb()->getFileRecord(parentPath); - bool isDir = type == CSYNC_FTW_TYPE_DIR; - while( !d.isRoot() && !(d.exists() && dirRec.isValid()) ) { - d.cdUp(); // returns true if the dir exists. - - parentPath = d.path(); - // cut the folder path - dirRec = folder->journalDb()->getFileRecord(parentPath); - - isDir = true; - } - if( dirRec.isValid() && !dirRec._remotePerm.isNull()) { - if( (isDir && !dirRec._remotePerm.contains("K")) - || (!isDir && !dirRec._remotePerm.contains("C")) ) { - status.set(SyncFileStatus::STATUS_ERROR); - } - } - } - return status; -} - - } // namespace OCC - diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index ca56c3e4e0..328d538011 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -56,6 +56,7 @@ set(libsync_SRCS propagateremotemkdir.cpp syncengine.cpp syncfilestatus.cpp + syncfilestatustracker.cpp syncjournaldb.cpp syncjournalfilerecord.cpp syncresult.cpp diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 6e238cfa13..44864d9998 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -93,6 +93,7 @@ SyncEngine::SyncEngine(AccountPtr account, const QString& localPath, csync_create(&_csync_ctx, localPath.toUtf8().data(), url_string.toUtf8().data()); csync_init(_csync_ctx); _excludedFiles.reset(new ExcludedFiles(&_csync_ctx->excludes)); + _syncFileStatusTracker.reset(new SyncFileStatusTracker(this)); _thread.setObjectName("SyncEngine_Thread"); } diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index b35cbe31fa..f78dad06bb 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -35,7 +35,7 @@ #include "syncfileitem.h" #include "progressdispatcher.h" #include "utility.h" -#include "syncfilestatus.h" +#include "syncfilestatustracker.h" #include "accountfwd.h" #include "discoveryphase.h" #include "checksums.h" @@ -75,10 +75,12 @@ public: * -1 means infinite */ void setNewBigFolderSizeLimit(qint64 limit) { _newBigFolderSizeLimit = limit; } + bool ignoreHiddenFiles() const { return _csync_ctx->ignore_hidden_files; } void setIgnoreHiddenFiles(bool ignore) { _csync_ctx->ignore_hidden_files = ignore; } ExcludedFiles &excludedFiles() { return *_excludedFiles; } Utility::StopWatch &stopWatch() { return _stopWatch; } + SyncFileStatusTracker &syncFileStatusTracker() { return *_syncFileStatusTracker; } /* Return true if we detected that another sync is needed to complete the sync */ bool isAnotherSyncNeeded() { return _anotherSyncNeeded; } @@ -93,7 +95,7 @@ public: AccountPtr account() const; SyncJournalDb *journal() const { return _journal; } - + QString localPath() const { return _localPath; } /** * Minimum age, in milisecond, of a file that can be uploaded. * Files more recent than that are not going to be uploaeded as they are considered @@ -211,6 +213,7 @@ private: QScopedPointer _progressInfo; QScopedPointer _excludedFiles; + QScopedPointer _syncFileStatusTracker; Utility::StopWatch _stopWatch; // maps the origin and the target of the folders that have been renamed diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp new file mode 100644 index 0000000000..e88459ba41 --- /dev/null +++ b/src/libsync/syncfilestatustracker.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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; version 2 of the License. + * + * 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 "syncfilestatustracker.h" +#include "filesystem.h" +#include "syncengine.h" +#include "syncjournaldb.h" +#include "syncjournalfilerecord.h" +#include "utility.h" + +#include +#include + +namespace OCC { + +/** + * Whether this item should get an ERROR icon through the Socket API. + * + * The Socket API should only present serious, permanent errors to the user. + * In particular SoftErrors should just retain their 'needs to be synced' + * icon as the problem is most likely going to resolve itself quickly and + * automatically. + */ +static bool showErrorInSocketApi(const SyncFileItem& item) +{ + const auto status = item._status; + return status == SyncFileItem::NormalError + || status == SyncFileItem::FatalError; +} + +static void addErroredSyncItemPathsToList(const SyncFileItemVector& items, QSet* set) { + foreach (const SyncFileItemPtr &item, items) { + if (showErrorInSocketApi(*item)) { + set->insert(item->_file); + } + } +} + +SyncFileStatusTracker::SyncFileStatusTracker(SyncEngine *syncEngine) + : _syncEngine(syncEngine) +{ + connect(syncEngine, SIGNAL(treeWalkResult(const SyncFileItemVector&)), + this, SLOT(slotThreadTreeWalkResult(const SyncFileItemVector&))); + connect(syncEngine, SIGNAL(aboutToPropagate(SyncFileItemVector&)), + this, SLOT(slotAboutToPropagate(SyncFileItemVector&))); + connect(syncEngine, SIGNAL(finished(bool)), SLOT(slotSyncFinished())); + connect(syncEngine, SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)), + this, SLOT(slotItemCompleted(const SyncFileItem &))); +} + +bool SyncFileStatusTracker::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) +{ + if (t == CSYNC_FTW_TYPE_DIR) { + if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) { + qDebug() << Q_FUNC_INFO << "Folder has error" << fn; + s->set(SyncFileStatus::STATUS_ERROR); + return true; + } + // If sync is running, check _syncedItems, possibly give it STATUS_EVAL (=syncing down) + if (_syncEngine->isSyncRunning()) { + if (_syncEngine->estimateState(fn, t, s)) { + return true; + } + } + return false; + } else if ( t== CSYNC_FTW_TYPE_FILE) { + // check if errorList has the directory/file + if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) { + s->set(SyncFileStatus::STATUS_ERROR); + return true; + } + // If sync running: _syncedItems -> SyncingState + if (_syncEngine->isSyncRunning()) { + if (_syncEngine->estimateState(fn, t, s)) { + return true; + } + } + } + return false; +} + + +/** + * Get status about a single file. + */ +SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName) +{ + QString file = _syncEngine->localPath(); + QString fileName = systemFileName.normalized(QString::NormalizationForm_C); + QString fileNameSlash = fileName; + + if(fileName != QLatin1String("/") && !fileName.isEmpty()) { + file += fileName; + } + + if( fileName.endsWith(QLatin1Char('/')) ) { + fileName.truncate(fileName.length()-1); + qDebug() << "Removed trailing slash: " << fileName; + } else { + fileNameSlash += QLatin1Char('/'); + } + + const QFileInfo fi(file); + if( !FileSystem::fileExists(file, fi) ) { + qDebug() << "OO File " << file << " is not existing"; + return SyncFileStatus(SyncFileStatus::STATUS_STAT_ERROR); + } + + // file is ignored? + // Qt considers .lnk files symlinks on Windows so we need to work + // around that here. + if( fi.isSymLink() +#ifdef Q_OS_WIN + && fi.suffix() != "lnk" +#endif + ) { + return SyncFileStatus(SyncFileStatus::STATUS_IGNORE); + } + + csync_ftw_type_e type = CSYNC_FTW_TYPE_FILE; + if( fi.isDir() ) { + type = CSYNC_FTW_TYPE_DIR; + } + + // Is it excluded? + if( _syncEngine->excludedFiles().isExcluded(file, _syncEngine->localPath(), _syncEngine->ignoreHiddenFiles()) ) { + return SyncFileStatus(SyncFileStatus::STATUS_IGNORE); + } + + // Error if it is in the selective sync blacklist + foreach(const auto &s, _syncEngine->journal()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList)) { + if (fileNameSlash.startsWith(s)) { + return SyncFileStatus(SyncFileStatus::STATUS_ERROR); + } + } + + SyncFileStatus status(SyncFileStatus::STATUS_NONE); + SyncJournalFileRecord rec = _syncEngine->journal()->getFileRecord(fileName ); + + if (estimateState(fileName, type, &status)) { + qDebug() << "Folder estimated status for" << fileName << "to" << status.toSocketAPIString(); + } else if (fileName == "") { + // sync folder itself + // FIXME: The new parent folder logic should take over this, treating the root the same as any folder. + } else if (type == CSYNC_FTW_TYPE_DIR) { + if (rec.isValid()) { + status.set(SyncFileStatus::STATUS_UPTODATE); + } else { + qDebug() << "Could not determine state for folder" << fileName << "will set STATUS_NEW"; + status.set(SyncFileStatus::STATUS_NEW); + } + } else if (type == CSYNC_FTW_TYPE_FILE) { + if (rec.isValid()) { + if( FileSystem::getModTime(fi.absoluteFilePath()) == Utility::qDateTimeToTime_t(rec._modtime) ) { + status.set(SyncFileStatus::STATUS_UPTODATE); + } else { + if (rec._remotePerm.isNull() || rec._remotePerm.contains("W") ) { + status.set(SyncFileStatus::STATUS_EVAL); + } else { + status.set(SyncFileStatus::STATUS_ERROR); + } + } + } else { + qDebug() << "Could not determine state for file" << fileName << "will set STATUS_NEW"; + status.set(SyncFileStatus::STATUS_NEW); + } + } + + if (rec.isValid() && rec._remotePerm.contains("S")) + status.setSharedWithMe(true); + + if (status.tag() == SyncFileStatus::STATUS_NEW) { + // check the parent folder if it is shared and if it is allowed to create a file/dir within + QDir d( fi.path() ); + auto parentPath = d.path(); + auto dirRec = _syncEngine->journal()->getFileRecord(parentPath); + bool isDir = type == CSYNC_FTW_TYPE_DIR; + while( !d.isRoot() && !(d.exists() && dirRec.isValid()) ) { + d.cdUp(); // returns true if the dir exists. + + parentPath = d.path(); + // cut the folder path + dirRec = _syncEngine->journal()->getFileRecord(parentPath); + + isDir = true; + } + if( dirRec.isValid() && !dirRec._remotePerm.isNull()) { + if( (isDir && !dirRec._remotePerm.contains("K")) + || (!isDir && !dirRec._remotePerm.contains("C")) ) { + status.set(SyncFileStatus::STATUS_ERROR); + } + } + } + return status; +} + +void SyncFileStatusTracker::slotThreadTreeWalkResult(const SyncFileItemVector& items) +{ + addErroredSyncItemPathsToList(items, &_stateLastSyncItemsWithErrorNew); +} + +void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items) +{ + addErroredSyncItemPathsToList(items, &_stateLastSyncItemsWithErrorNew); +} + +void SyncFileStatusTracker::slotSyncFinished() +{ + _stateLastSyncItemsWithError = _stateLastSyncItemsWithErrorNew; + _stateLastSyncItemsWithErrorNew.clear(); +} + +void SyncFileStatusTracker::slotItemCompleted(const SyncFileItem &item) +{ + if (showErrorInSocketApi(item)) { + _stateLastSyncItemsWithErrorNew.insert(item._file); + } +} + +} diff --git a/src/libsync/syncfilestatustracker.h b/src/libsync/syncfilestatustracker.h new file mode 100644 index 0000000000..ac8a8233c6 --- /dev/null +++ b/src/libsync/syncfilestatustracker.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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; version 2 of the License. + * + * 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 SYNCFILESTATUSTRACKER_H +#define SYNCFILESTATUSTRACKER_H + +#include "ownsql.h" +#include "syncfileitem.h" +#include "syncfilestatus.h" +#include +#include + +namespace OCC { + +class SyncEngine; + +class OWNCLOUDSYNC_EXPORT SyncFileStatusTracker : public QObject +{ +public: + SyncFileStatusTracker(SyncEngine *syncEngine); + SyncFileStatus fileStatus(const QString& systemFileName); + +private slots: + void slotThreadTreeWalkResult(const SyncFileItemVector& items); + void slotAboutToPropagate(SyncFileItemVector& items); + void slotSyncFinished(); + void slotItemCompleted(const SyncFileItem &item); + +private: + bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s); + + SyncEngine *_syncEngine; + // SocketAPI: Cache files and folders that had errors so that they can + // get a red ERROR icon. + QSet _stateLastSyncItemsWithErrorNew; // gets moved to _stateLastSyncItemsWithError at end of sync + QSet _stateLastSyncItemsWithError; +}; + +} + +#endif From ea5e6d367bdceab86e0f786ef5d3d98a43f856ed Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Mon, 21 Mar 2016 12:35:57 +0100 Subject: [PATCH 09/17] Connect the SocketApi directly to the SyncFileStatusTracker Don't go through the Folder->ProgressDispatcher->SocketApi route and keep the path logic in SyncFileStatusTracker. --- src/gui/folder.cpp | 6 ---- src/gui/folder.h | 1 - src/gui/folderman.cpp | 4 +++ src/gui/socketapi.cpp | 45 ++------------------------- src/gui/socketapi.h | 5 +-- src/libsync/progressdispatcher.h | 2 -- src/libsync/syncfilestatustracker.cpp | 32 +++++++++++++++++-- src/libsync/syncfilestatustracker.h | 5 +++ 8 files changed, 42 insertions(+), 58 deletions(-) diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 9cc7aef0e6..bfa857ff12 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -114,7 +114,6 @@ Folder::Folder(const FolderDefinition& definition, connect(_engine.data(), SIGNAL(transmissionProgress(ProgressInfo)), this, SLOT(slotTransmissionProgress(ProgressInfo))); connect(_engine.data(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)), this, SLOT(slotItemCompleted(const SyncFileItem &, const PropagatorJob &))); - connect(_engine.data(), SIGNAL(syncItemDiscovered(const SyncFileItem &)), this, SLOT(slotSyncItemDiscovered(const SyncFileItem &))); connect(_engine.data(), SIGNAL(newBigFolder(QString)), this, SLOT(slotNewBigFolderDiscovered(QString))); } @@ -913,11 +912,6 @@ void Folder::slotItemCompleted(const SyncFileItem &item, const PropagatorJob& jo emit ProgressDispatcher::instance()->itemCompleted(alias(), item, job); } -void Folder::slotSyncItemDiscovered(const SyncFileItem & item) -{ - emit ProgressDispatcher::instance()->syncItemDiscovered(alias(), item); -} - void Folder::slotNewBigFolderDiscovered(const QString &newF) { auto newFolder = newF; diff --git a/src/gui/folder.h b/src/gui/folder.h index a76b9e2557..0603e7a64c 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -248,7 +248,6 @@ private slots: void slotFolderDiscovered(bool local, QString folderName); void slotTransmissionProgress(const ProgressInfo& pi); void slotItemCompleted(const SyncFileItem&, const PropagatorJob&); - void slotSyncItemDiscovered(const SyncFileItem & item); void slotRunEtagJob(); void etagRetreived(const QString &); diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 661d0f3ce2..006555f3c8 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -105,6 +105,8 @@ void FolderMan::unloadFolder( Folder *f ) this, SLOT(slotForwardFolderSyncStateChange())); disconnect(f, SIGNAL(syncPausedChanged(Folder*,bool)), this, SLOT(slotFolderSyncPaused(Folder*,bool))); + disconnect(&f->syncEngine().syncFileStatusTracker(), SIGNAL(fileStatusChanged(const QString &, SyncFileStatus)), + _socketApi.data(), SLOT(slotFileStatusChanged(const QString &, SyncFileStatus))); } int FolderMan::unloadAndDeleteAllFolders() @@ -796,6 +798,8 @@ Folder* FolderMan::addFolderInternal(const FolderDefinition& folderDefinition, A connect(folder, SIGNAL(syncFinished(SyncResult)), SLOT(slotFolderSyncFinished(SyncResult))); connect(folder, SIGNAL(syncStateChange()), SLOT(slotForwardFolderSyncStateChange())); connect(folder, SIGNAL(syncPausedChanged(Folder*,bool)), SLOT(slotFolderSyncPaused(Folder*,bool))); + connect(&folder->syncEngine().syncFileStatusTracker(), SIGNAL(fileStatusChanged(const QString &, SyncFileStatus)), + _socketApi.data(), SLOT(slotFileStatusChanged(const QString &, SyncFileStatus))); registerFolderMonitor(folder); return folder; diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index b3c7161f6c..338fdfbbff 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -114,10 +114,6 @@ SocketApi::SocketApi(QObject* parent) // folder watcher connect(FolderMan::instance(), SIGNAL(folderSyncStateChange(Folder*)), this, SLOT(slotUpdateFolderView(Folder*))); - connect(ProgressDispatcher::instance(), SIGNAL(itemCompleted(QString, const SyncFileItem &, const PropagatorJob &)), - SLOT(slotItemCompleted(QString, const SyncFileItem &))); - connect(ProgressDispatcher::instance(), SIGNAL(syncItemDiscovered(QString, const SyncFileItem &)), - this, SLOT(slotSyncItemDiscovered(QString, const SyncFileItem &))); } SocketApi::~SocketApi() @@ -225,48 +221,11 @@ void SocketApi::slotUpdateFolderView(Folder *f) } } -void SocketApi::slotItemCompleted(const QString &folder, const SyncFileItem &item) +void SocketApi::slotFileStatusChanged(const QString& systemFileName, SyncFileStatus fileStatus) { - if (_listeners.isEmpty()) { - return; - } - - Folder *f = FolderMan::instance()->folder(folder); - if (!f) { - return; - } - - auto status = f->syncEngine().syncFileStatusTracker().fileStatus(item.destination()); - const QString path = f->path() + item.destination(); - broadcastMessage(QLatin1String("STATUS"), path, status.toSocketAPIString()); + broadcastMessage(QLatin1String("STATUS"), systemFileName, fileStatus.toSocketAPIString()); } -void SocketApi::slotSyncItemDiscovered(const QString &folder, const SyncFileItem &item) -{ - if (_listeners.isEmpty()) { - return; - } - - Folder *f = FolderMan::instance()->folder(folder); - if (!f) { - return; - } - - QString path = f->path() + item.destination(); - - // the trailing slash for directories must be appended as the filenames coming in - // from the plugins have that too. Otherwise the matching entry item is not found - // in the plugin. - if( item._type == SyncFileItem::Type::Directory ) { - path += QLatin1Char('/'); - } - - const QString command = QLatin1String("SYNC"); - broadcastMessage(QLatin1String("STATUS"), path, command); -} - - - void SocketApi::sendMessage(QIODevice *socket, const QString& message, bool doWait) { DEBUG << "Sending message: " << message; diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index 5e29def36e..f01014225f 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -60,12 +60,9 @@ private slots: void slotNewConnection(); void onLostConnection(); void slotReadSocket(); - void slotItemCompleted(const QString &, const SyncFileItem &); - void slotSyncItemDiscovered(const QString &, const SyncFileItem &); + void slotFileStatusChanged(const QString& systemFileName, SyncFileStatus fileStatus); private: - SyncFileStatus fileStatus(Folder *folder, const QString& systemFileName); - void sendMessage(QIODevice* socket, const QString& message, bool doWait = false); void broadcastMessage(const QString& verb, const QString &path, const QString &status = QString::null, bool doWait = false); diff --git a/src/libsync/progressdispatcher.h b/src/libsync/progressdispatcher.h index a89e0fd3a2..ec555c3131 100644 --- a/src/libsync/progressdispatcher.h +++ b/src/libsync/progressdispatcher.h @@ -239,8 +239,6 @@ signals: const SyncFileItem & item, const PropagatorJob & job); - void syncItemDiscovered(const QString &folder, const SyncFileItem & item); - protected: void setProgressInfo(const QString& folder, const ProgressInfo& progress); diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index e88459ba41..674c608e53 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -54,8 +54,10 @@ SyncFileStatusTracker::SyncFileStatusTracker(SyncEngine *syncEngine) connect(syncEngine, SIGNAL(aboutToPropagate(SyncFileItemVector&)), this, SLOT(slotAboutToPropagate(SyncFileItemVector&))); connect(syncEngine, SIGNAL(finished(bool)), SLOT(slotSyncFinished())); - connect(syncEngine, SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)), - this, SLOT(slotItemCompleted(const SyncFileItem &))); + connect(syncEngine, SIGNAL(itemCompleted(const SyncFileItem&, const PropagatorJob&)), + this, SLOT(slotItemCompleted(const SyncFileItem&))); + connect(syncEngine, SIGNAL(syncItemDiscovered(const SyncFileItem&)), + this, SLOT(slotItemDiscovered(const SyncFileItem&))); } bool SyncFileStatusTracker::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) @@ -225,6 +227,32 @@ void SyncFileStatusTracker::slotItemCompleted(const SyncFileItem &item) if (showErrorInSocketApi(item)) { _stateLastSyncItemsWithErrorNew.insert(item._file); } + + QString systemFileName = _syncEngine->localPath() + item.destination(); + + // the trailing slash for directories must be appended as the filenames coming in + // from the plugins have that too. Otherwise the matching entry item is not found + // in the plugin. + if( item._type == SyncFileItem::Type::Directory ) { + systemFileName += QLatin1Char('/'); + } + + auto status = fileStatus(item.destination()); + emit fileStatusChanged(systemFileName, status); +} + +void SyncFileStatusTracker::slotItemDiscovered(const SyncFileItem &item) +{ + QString systemFileName = _syncEngine->localPath() + item.destination(); + + // the trailing slash for directories must be appended as the filenames coming in + // from the plugins have that too. Otherwise the matching entry item is not found + // in the plugin. + if( item._type == SyncFileItem::Type::Directory ) { + systemFileName += QLatin1Char('/'); + } + + emit fileStatusChanged(systemFileName, SyncFileStatus(SyncFileStatus::STATUS_EVAL)); } } diff --git a/src/libsync/syncfilestatustracker.h b/src/libsync/syncfilestatustracker.h index ac8a8233c6..4ed067ff18 100644 --- a/src/libsync/syncfilestatustracker.h +++ b/src/libsync/syncfilestatustracker.h @@ -26,15 +26,20 @@ class SyncEngine; class OWNCLOUDSYNC_EXPORT SyncFileStatusTracker : public QObject { + Q_OBJECT public: SyncFileStatusTracker(SyncEngine *syncEngine); SyncFileStatus fileStatus(const QString& systemFileName); +signals: + void fileStatusChanged(const QString& systemFileName, SyncFileStatus fileStatus); + private slots: void slotThreadTreeWalkResult(const SyncFileItemVector& items); void slotAboutToPropagate(SyncFileItemVector& items); void slotSyncFinished(); void slotItemCompleted(const SyncFileItem &item); + void slotItemDiscovered(const SyncFileItem &item); private: bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s); From 69aa39f1f645f262106ef103e7c373cda2f57654 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Thu, 24 Mar 2016 18:20:49 +0100 Subject: [PATCH 10/17] Don's use SyncFileStatus for Folder::createGuiLog SyncFileStatus' purpose is to track overlay icon status. Instead of putting comments and default: clauses in switch on both sides about unused enums, use different enums. This also remove STATUS_NEW which is the equivalent of STATUS_SYNC in all shell extension implementations, and remove STATUS_UPDATED and STATUS_STAT_ERROR which have the same semantic as STATUS_UPTODATE and STATUS__ERROR. --- src/gui/folder.cpp | 32 ++++++++++------------ src/gui/folder.h | 12 +++++++-- src/gui/socketapi.cpp | 6 ++--- src/gui/socketapi.h | 1 + src/libsync/syncengine.cpp | 8 +++--- src/libsync/syncfilestatus.cpp | 21 +++++---------- src/libsync/syncfilestatus.h | 17 ++++-------- src/libsync/syncfilestatustracker.cpp | 39 ++++++++++++++------------- 8 files changed, 62 insertions(+), 74 deletions(-) diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index bfa857ff12..c00b95da6b 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -441,32 +441,32 @@ void Folder::bubbleUpSyncResult() _syncResult.setWarnCount(ignoredItems); if( firstItemNew ) { - createGuiLog( firstItemNew->_file, SyncFileStatus::STATUS_NEW, newItems ); + createGuiLog( firstItemNew->_file, LogStatusNew, newItems ); } if( firstItemDeleted ) { - createGuiLog( firstItemDeleted->_file, SyncFileStatus::STATUS_REMOVE, removedItems ); + createGuiLog( firstItemDeleted->_file, LogStatusRemove, removedItems ); } if( firstItemUpdated ) { - createGuiLog( firstItemUpdated->_file, SyncFileStatus::STATUS_UPDATED, updatedItems ); + createGuiLog( firstItemUpdated->_file, LogStatusUpdated, updatedItems ); } if( firstItemRenamed ) { - SyncFileStatus status(SyncFileStatus::STATUS_RENAME); + LogStatus status(LogStatusRename); // if the path changes it's rather a move QDir renTarget = QFileInfo(firstItemRenamed->_renameTarget).dir(); QDir renSource = QFileInfo(firstItemRenamed->_file).dir(); if(renTarget != renSource) { - status.set(SyncFileStatus::STATUS_MOVE); + status = LogStatusMove; } createGuiLog( firstItemRenamed->_originalFile, status, renamedItems, firstItemRenamed->_renameTarget ); } - createGuiLog( firstItemError->_file, SyncFileStatus::STATUS_ERROR, errorItems ); + createGuiLog( firstItemError->_file, LogStatusError, errorItems ); qDebug() << "OO folder slotSyncFinished: result: " << int(_syncResult.status()); } -void Folder::createGuiLog( const QString& filename, SyncFileStatus status, int count, +void Folder::createGuiLog( const QString& filename, LogStatus status, int count, const QString& renameTarget ) { if(count > 0) { @@ -475,53 +475,49 @@ void Folder::createGuiLog( const QString& filename, SyncFileStatus status, int c QString file = QDir::toNativeSeparators(filename); QString text; - // not all possible values of status are evaluated here because the others - // are not used in the calling function. Please check there. - switch (status.tag()) { - case SyncFileStatus::STATUS_REMOVE: + switch (status) { + case LogStatusRemove: if( count > 1 ) { text = tr("%1 and %2 other files have been removed.", "%1 names a file.").arg(file).arg(count-1); } else { text = tr("%1 has been removed.", "%1 names a file.").arg(file); } break; - case SyncFileStatus::STATUS_NEW: + case LogStatusNew: if( count > 1 ) { text = tr("%1 and %2 other files have been downloaded.", "%1 names a file.").arg(file).arg(count-1); } else { text = tr("%1 has been downloaded.", "%1 names a file.").arg(file); } break; - case SyncFileStatus::STATUS_UPDATED: + case LogStatusUpdated: if( count > 1 ) { text = tr("%1 and %2 other files have been updated.").arg(file).arg(count-1); } else { text = tr("%1 has been updated.", "%1 names a file.").arg(file); } break; - case SyncFileStatus::STATUS_RENAME: + case LogStatusRename: if( count > 1 ) { text = tr("%1 has been renamed to %2 and %3 other files have been renamed.").arg(file).arg(renameTarget).arg(count-1); } else { text = tr("%1 has been renamed to %2.", "%1 and %2 name files.").arg(file).arg(renameTarget); } break; - case SyncFileStatus::STATUS_MOVE: + case LogStatusMove: if( count > 1 ) { text = tr("%1 has been moved to %2 and %3 other files have been moved.").arg(file).arg(renameTarget).arg(count-1); } else { text = tr("%1 has been moved to %2.").arg(file).arg(renameTarget); } break; - case SyncFileStatus::STATUS_ERROR: + case LogStatusError: if( count > 1 ) { text = tr("%1 and %2 other files could not be synced due to errors. See the log for details.", "%1 names a file.").arg(file).arg(count-1); } else { text = tr("%1 could not be synced due to an error. See the log for details.").arg(file); } break; - default: - break; } if( !text.isEmpty() ) { diff --git a/src/gui/folder.h b/src/gui/folder.h index 0603e7a64c..55035c491a 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -21,7 +21,6 @@ #include "progressdispatcher.h" #include "syncjournaldb.h" #include "clientproxy.h" -#include "syncfilestatus.h" #include "networkjobs.h" #include @@ -266,7 +265,16 @@ private: void checkLocalPath(); - void createGuiLog(const QString& filename, SyncFileStatus status, int count, + enum LogStatus { + LogStatusRemove, + LogStatusRename, + LogStatusMove, + LogStatusNew, + LogStatusError, + LogStatusUpdated + }; + + void createGuiLog(const QString& filename, LogStatus status, int count, const QString& renameTarget = QString::null ); AccountStatePtr _accountState; diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 338fdfbbff..754291e804 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -330,8 +330,7 @@ void SocketApi::command_SHARE(const QString& localFile, QIODevice* socket) SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file); // Verify the file is on the server (to our knowledge of course) - if (fileStatus.tag() != SyncFileStatus::STATUS_UPTODATE && - fileStatus.tag() != SyncFileStatus::STATUS_UPDATED) { + if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) { const QString message = QLatin1String("SHARE:NOTSYNCED:")+QDir::toNativeSeparators(localFile); sendMessage(socket, message); return; @@ -386,8 +385,7 @@ void SocketApi::command_SHARE_STATUS(const QString &localFile, QIODevice *socket SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file); // Verify the file is on the server (to our knowledge of course) - if (fileStatus.tag() != SyncFileStatus::STATUS_UPTODATE && - fileStatus.tag() != SyncFileStatus::STATUS_UPDATED) { + if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) { const QString message = QLatin1String("SHARE_STATUS:NOTSYNCED:")+QDir::toNativeSeparators(localFile); sendMessage(socket, message); return; diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index f01014225f..e3f1e481a9 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -17,6 +17,7 @@ #define SOCKETAPI_H #include "syncfileitem.h" +#include "syncfilestatus.h" #include "ownsql.h" #if defined(Q_OS_MAC) diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 44864d9998..3e119face8 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -1321,13 +1321,13 @@ bool SyncEngine::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s item->_file == fn || item->_renameTarget == fn /* the same directory or file */) { if (item->_status == SyncFileItem::NormalError || item->_status == SyncFileItem::FatalError) - s->set(SyncFileStatus::STATUS_ERROR); + s->set(SyncFileStatus::StatusError); else if (item->_status == SyncFileItem::FileIgnored) - s->set(SyncFileStatus::STATUS_IGNORE); + s->set(SyncFileStatus::StatusIgnore); else if (item->_status == SyncFileItem::Success) - s->set(SyncFileStatus::STATUS_UPDATED); + s->set(SyncFileStatus::StatusUpToDate); else - s->set(SyncFileStatus::STATUS_EVAL); + s->set(SyncFileStatus::StatusSync); qDebug() << Q_FUNC_INFO << "Setting" << fn << "to" << s->toSocketAPIString(); return true; } diff --git a/src/libsync/syncfilestatus.cpp b/src/libsync/syncfilestatus.cpp index 313be6d517..6d234a3bdb 100644 --- a/src/libsync/syncfilestatus.cpp +++ b/src/libsync/syncfilestatus.cpp @@ -17,7 +17,7 @@ namespace OCC { SyncFileStatus::SyncFileStatus() - :_tag(STATUS_NONE), _sharedWithMe(false) + :_tag(StatusNone), _sharedWithMe(false) { } @@ -53,30 +53,21 @@ QString SyncFileStatus::toSocketAPIString() const switch(_tag) { - case STATUS_NONE: + case StatusNone: statusString = QLatin1String("NONE"); break; - case STATUS_EVAL: + case StatusSync: statusString = QLatin1String("SYNC"); break; - case STATUS_NEW: - statusString = QLatin1String("NEW"); - break; - case STATUS_IGNORE: + case StatusIgnore: statusString = QLatin1String("IGNORE"); break; - case STATUS_UPTODATE: - case STATUS_UPDATED: + case StatusUpToDate: statusString = QLatin1String("OK"); break; - case STATUS_STAT_ERROR: - case STATUS_ERROR: + case StatusError: statusString = QLatin1String("ERROR"); break; - default: - qWarning() << "This status should not be here:" << _tag; - Q_ASSERT(false); - statusString = QLatin1String("NONE"); } if(_sharedWithMe) { statusString += QLatin1String("+SWM"); diff --git a/src/libsync/syncfilestatus.h b/src/libsync/syncfilestatus.h index 28cb67fe53..1af254f7ae 100644 --- a/src/libsync/syncfilestatus.h +++ b/src/libsync/syncfilestatus.h @@ -28,18 +28,11 @@ class OWNCLOUDSYNC_EXPORT SyncFileStatus { public: enum SyncFileStatusTag { - STATUS_NONE, - STATUS_EVAL, - STATUS_REMOVE, - STATUS_RENAME, - STATUS_MOVE, - STATUS_NEW, - STATUS_CONFLICT, - STATUS_IGNORE, - STATUS_UPTODATE, - STATUS_STAT_ERROR, - STATUS_ERROR, - STATUS_UPDATED + StatusNone, + StatusSync, + StatusIgnore, + StatusUpToDate, + StatusError, }; SyncFileStatus(); diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index 674c608e53..86da7b3422 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -65,10 +65,10 @@ bool SyncFileStatusTracker::estimateState(QString fn, csync_ftw_type_e t, SyncFi if (t == CSYNC_FTW_TYPE_DIR) { if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) { qDebug() << Q_FUNC_INFO << "Folder has error" << fn; - s->set(SyncFileStatus::STATUS_ERROR); + s->set(SyncFileStatus::StatusError); return true; } - // If sync is running, check _syncedItems, possibly give it STATUS_EVAL (=syncing down) + // If sync is running, check _syncedItems, possibly give it StatusSync if (_syncEngine->isSyncRunning()) { if (_syncEngine->estimateState(fn, t, s)) { return true; @@ -78,7 +78,7 @@ bool SyncFileStatusTracker::estimateState(QString fn, csync_ftw_type_e t, SyncFi } else if ( t== CSYNC_FTW_TYPE_FILE) { // check if errorList has the directory/file if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) { - s->set(SyncFileStatus::STATUS_ERROR); + s->set(SyncFileStatus::StatusError); return true; } // If sync running: _syncedItems -> SyncingState @@ -115,7 +115,7 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName) const QFileInfo fi(file); if( !FileSystem::fileExists(file, fi) ) { qDebug() << "OO File " << file << " is not existing"; - return SyncFileStatus(SyncFileStatus::STATUS_STAT_ERROR); + return SyncFileStatus(SyncFileStatus::StatusError); } // file is ignored? @@ -126,7 +126,7 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName) && fi.suffix() != "lnk" #endif ) { - return SyncFileStatus(SyncFileStatus::STATUS_IGNORE); + return SyncFileStatus(SyncFileStatus::StatusIgnore); } csync_ftw_type_e type = CSYNC_FTW_TYPE_FILE; @@ -136,17 +136,17 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName) // Is it excluded? if( _syncEngine->excludedFiles().isExcluded(file, _syncEngine->localPath(), _syncEngine->ignoreHiddenFiles()) ) { - return SyncFileStatus(SyncFileStatus::STATUS_IGNORE); + return SyncFileStatus(SyncFileStatus::StatusIgnore); } // Error if it is in the selective sync blacklist foreach(const auto &s, _syncEngine->journal()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList)) { if (fileNameSlash.startsWith(s)) { - return SyncFileStatus(SyncFileStatus::STATUS_ERROR); + return SyncFileStatus(SyncFileStatus::StatusError); } } - SyncFileStatus status(SyncFileStatus::STATUS_NONE); + SyncFileStatus status(SyncFileStatus::StatusNone); SyncJournalFileRecord rec = _syncEngine->journal()->getFileRecord(fileName ); if (estimateState(fileName, type, &status)) { @@ -156,32 +156,33 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName) // FIXME: The new parent folder logic should take over this, treating the root the same as any folder. } else if (type == CSYNC_FTW_TYPE_DIR) { if (rec.isValid()) { - status.set(SyncFileStatus::STATUS_UPTODATE); + status.set(SyncFileStatus::StatusUpToDate); } else { - qDebug() << "Could not determine state for folder" << fileName << "will set STATUS_NEW"; - status.set(SyncFileStatus::STATUS_NEW); + qDebug() << "Could not determine state for folder" << fileName << "will set StatusSync"; + status.set(SyncFileStatus::StatusSync); } } else if (type == CSYNC_FTW_TYPE_FILE) { if (rec.isValid()) { if( FileSystem::getModTime(fi.absoluteFilePath()) == Utility::qDateTimeToTime_t(rec._modtime) ) { - status.set(SyncFileStatus::STATUS_UPTODATE); + status.set(SyncFileStatus::StatusUpToDate); } else { if (rec._remotePerm.isNull() || rec._remotePerm.contains("W") ) { - status.set(SyncFileStatus::STATUS_EVAL); + status.set(SyncFileStatus::StatusSync); } else { - status.set(SyncFileStatus::STATUS_ERROR); + status.set(SyncFileStatus::StatusError); } } } else { - qDebug() << "Could not determine state for file" << fileName << "will set STATUS_NEW"; - status.set(SyncFileStatus::STATUS_NEW); + qDebug() << "Could not determine state for file" << fileName << "will set StatusSync"; + status.set(SyncFileStatus::StatusSync); } } if (rec.isValid() && rec._remotePerm.contains("S")) status.setSharedWithMe(true); - if (status.tag() == SyncFileStatus::STATUS_NEW) { + // FIXME: Wrong, but will go away + if (status.tag() == SyncFileStatus::StatusSync) { // check the parent folder if it is shared and if it is allowed to create a file/dir within QDir d( fi.path() ); auto parentPath = d.path(); @@ -199,7 +200,7 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName) if( dirRec.isValid() && !dirRec._remotePerm.isNull()) { if( (isDir && !dirRec._remotePerm.contains("K")) || (!isDir && !dirRec._remotePerm.contains("C")) ) { - status.set(SyncFileStatus::STATUS_ERROR); + status.set(SyncFileStatus::StatusError); } } } @@ -252,7 +253,7 @@ void SyncFileStatusTracker::slotItemDiscovered(const SyncFileItem &item) systemFileName += QLatin1Char('/'); } - emit fileStatusChanged(systemFileName, SyncFileStatus(SyncFileStatus::STATUS_EVAL)); + emit fileStatusChanged(systemFileName, SyncFileStatus(SyncFileStatus::StatusSync)); } } From 82190eaa81e6736cb924beb01671be768a921b05 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Thu, 24 Mar 2016 17:42:04 +0100 Subject: [PATCH 11/17] Refactor the overlay icon logic to show errors as a warning for parent folders #3634 This also remove all smartness from the SocketApi about the status of a file and solely use info from the current and last sync. This simplifies the logic a lot and prevents any discrepancy between the status shown in the activity log and the one displayed on the overlay icon of a file. The main benefit of the additional simplicity is that we are able to push all new status of a file reliably (including warnings for parent folders) to properly update the icon on overlay implementations that don't allow us invalidating the status cache, like on OS X. Both errors and warning from the last sync are now kept in a set, which is used to also affect parent folders of an error. To make sure that errors don't become warning icons on a second sync, SyncFileItem::_hasBlacklistEntry is also interpreted as an error. This also renames StatusIgnore to StatusWarning to match this semantic. SyncEngine::aboutToPropagate is used in favor of SyncEngine::syncItemDiscovered since the latter is emitted before file permission warnings are set on the SyncFileItem. SyncEngine::finished is not used since we have all the needed information in SyncEngine::itemCompleted. --- src/libsync/syncengine.cpp | 29 +-- src/libsync/syncengine.h | 2 +- src/libsync/syncfilestatus.cpp | 3 +- src/libsync/syncfilestatus.h | 2 +- src/libsync/syncfilestatustracker.cpp | 275 ++++++++++---------------- src/libsync/syncfilestatustracker.h | 34 ++-- src/libsync/utility.cpp | 12 -- src/libsync/utility.h | 5 - 8 files changed, 128 insertions(+), 234 deletions(-) diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 3e119face8..a7f75b422a 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -1306,33 +1306,14 @@ void SyncEngine::restoreOldFiles() } } -bool SyncEngine::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) +SyncFileItem* SyncEngine::findSyncItem(const QString &fileName) const { - Q_UNUSED(t); - QString pat(fn); - if( t == CSYNC_FTW_TYPE_DIR && ! fn.endsWith(QLatin1Char('/'))) { - pat.append(QLatin1Char('/')); - } - Q_FOREACH(const SyncFileItemPtr &item, _syncedItems) { - //qDebug() << Q_FUNC_INFO << fn << item->_status << item->_file << fn.startsWith(item->_file) << item->_file.startsWith(fn); - - if (item->_file.startsWith(pat) || - item->_file == fn || item->_renameTarget == fn /* the same directory or file */) { - if (item->_status == SyncFileItem::NormalError - || item->_status == SyncFileItem::FatalError) - s->set(SyncFileStatus::StatusError); - else if (item->_status == SyncFileItem::FileIgnored) - s->set(SyncFileStatus::StatusIgnore); - else if (item->_status == SyncFileItem::Success) - s->set(SyncFileStatus::StatusUpToDate); - else - s->set(SyncFileStatus::StatusSync); - qDebug() << Q_FUNC_INFO << "Setting" << fn << "to" << s->toSocketAPIString(); - return true; - } + // Directories will appear in this list as well, and will get their status set once all children have been propagated + if ((item->_file == fileName || item->_renameTarget == fileName)) + return item.data(); } - return false; + return 0; } qint64 SyncEngine::timeSinceFileTouched(const QString& fn) const diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index f78dad06bb..934e0728c7 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -85,7 +85,7 @@ public: /* Return true if we detected that another sync is needed to complete the sync */ bool isAnotherSyncNeeded() { return _anotherSyncNeeded; } - bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s); + SyncFileItem* findSyncItem(const QString &fileName) const; /** Get the ms since a file was touched, or -1 if it wasn't. * diff --git a/src/libsync/syncfilestatus.cpp b/src/libsync/syncfilestatus.cpp index 6d234a3bdb..2afd5c0995 100644 --- a/src/libsync/syncfilestatus.cpp +++ b/src/libsync/syncfilestatus.cpp @@ -59,7 +59,8 @@ QString SyncFileStatus::toSocketAPIString() const case StatusSync: statusString = QLatin1String("SYNC"); break; - case StatusIgnore: + case StatusWarning: + // The protocol says IGNORE, but all implementations show a yellow warning sign. statusString = QLatin1String("IGNORE"); break; case StatusUpToDate: diff --git a/src/libsync/syncfilestatus.h b/src/libsync/syncfilestatus.h index 1af254f7ae..fddcf1af0b 100644 --- a/src/libsync/syncfilestatus.h +++ b/src/libsync/syncfilestatus.h @@ -30,7 +30,7 @@ public: enum SyncFileStatusTag { StatusNone, StatusSync, - StatusIgnore, + StatusWarning, StatusUpToDate, StatusError, }; diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index 86da7b3422..5ae8b9692a 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) by Klaas Freitag + * Copyright (C) by Jocelyn Turcotte * * 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 @@ -12,17 +13,25 @@ */ #include "syncfilestatustracker.h" -#include "filesystem.h" #include "syncengine.h" #include "syncjournaldb.h" #include "syncjournalfilerecord.h" -#include "utility.h" - -#include -#include namespace OCC { +static SyncFileStatus::SyncFileStatusTag lookupProblem(const QString &pathToMatch, const std::set &set) +{ + for (auto it = set.cbegin(); it != set.cend(); ++it) { + qDebug() << Q_FUNC_INFO << pathToMatch << it->severity << it->path; + auto problemPath = it->path; + if (problemPath == pathToMatch) + return it->severity; + else if (it->severity == SyncFileStatus::StatusError && problemPath.startsWith(pathToMatch)) + return SyncFileStatus::StatusWarning; + } + return SyncFileStatus::StatusNone; +} + /** * Whether this item should get an ERROR icon through the Socket API. * @@ -31,229 +40,143 @@ namespace OCC { * icon as the problem is most likely going to resolve itself quickly and * automatically. */ -static bool showErrorInSocketApi(const SyncFileItem& item) +static inline bool showErrorInSocketApi(const SyncFileItem& item) { const auto status = item._status; return status == SyncFileItem::NormalError - || status == SyncFileItem::FatalError; + || status == SyncFileItem::FatalError + || item._hasBlacklistEntry; } -static void addErroredSyncItemPathsToList(const SyncFileItemVector& items, QSet* set) { - foreach (const SyncFileItemPtr &item, items) { - if (showErrorInSocketApi(*item)) { - set->insert(item->_file); - } - } +static inline bool showWarningInSocketApi(const SyncFileItem& item) +{ + const auto status = item._status; + return status == SyncFileItem::FileIgnored + || status == SyncFileItem::Conflict + || status == SyncFileItem::Restoration; } SyncFileStatusTracker::SyncFileStatusTracker(SyncEngine *syncEngine) : _syncEngine(syncEngine) { - connect(syncEngine, SIGNAL(treeWalkResult(const SyncFileItemVector&)), - this, SLOT(slotThreadTreeWalkResult(const SyncFileItemVector&))); connect(syncEngine, SIGNAL(aboutToPropagate(SyncFileItemVector&)), this, SLOT(slotAboutToPropagate(SyncFileItemVector&))); - connect(syncEngine, SIGNAL(finished(bool)), SLOT(slotSyncFinished())); connect(syncEngine, SIGNAL(itemCompleted(const SyncFileItem&, const PropagatorJob&)), this, SLOT(slotItemCompleted(const SyncFileItem&))); - connect(syncEngine, SIGNAL(syncItemDiscovered(const SyncFileItem&)), - this, SLOT(slotItemDiscovered(const SyncFileItem&))); -} - -bool SyncFileStatusTracker::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) -{ - if (t == CSYNC_FTW_TYPE_DIR) { - if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) { - qDebug() << Q_FUNC_INFO << "Folder has error" << fn; - s->set(SyncFileStatus::StatusError); - return true; - } - // If sync is running, check _syncedItems, possibly give it StatusSync - if (_syncEngine->isSyncRunning()) { - if (_syncEngine->estimateState(fn, t, s)) { - return true; - } - } - return false; - } else if ( t== CSYNC_FTW_TYPE_FILE) { - // check if errorList has the directory/file - if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) { - s->set(SyncFileStatus::StatusError); - return true; - } - // If sync running: _syncedItems -> SyncingState - if (_syncEngine->isSyncRunning()) { - if (_syncEngine->estimateState(fn, t, s)) { - return true; - } - } - } - return false; } -/** - * Get status about a single file. - */ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName) { - QString file = _syncEngine->localPath(); QString fileName = systemFileName.normalized(QString::NormalizationForm_C); - QString fileNameSlash = fileName; - - if(fileName != QLatin1String("/") && !fileName.isEmpty()) { - file += fileName; - } - if( fileName.endsWith(QLatin1Char('/')) ) { fileName.truncate(fileName.length()-1); qDebug() << "Removed trailing slash: " << fileName; - } else { - fileNameSlash += QLatin1Char('/'); } - const QFileInfo fi(file); - if( !FileSystem::fileExists(file, fi) ) { - qDebug() << "OO File " << file << " is not existing"; - return SyncFileStatus(SyncFileStatus::StatusError); - } + SyncFileItem* item = _syncEngine->findSyncItem(fileName); + if (item) + return fileStatus(*item); - // file is ignored? - // Qt considers .lnk files symlinks on Windows so we need to work - // around that here. - if( fi.isSymLink() -#ifdef Q_OS_WIN - && fi.suffix() != "lnk" -#endif - ) { - return SyncFileStatus(SyncFileStatus::StatusIgnore); - } + // If we're not currently syncing that file, look it up in the database to know if it's shared + SyncJournalFileRecord rec = _syncEngine->journal()->getFileRecord(fileName); + if (rec.isValid()) + return fileStatus(rec.toSyncFileItem()); - csync_ftw_type_e type = CSYNC_FTW_TYPE_FILE; - if( fi.isDir() ) { - type = CSYNC_FTW_TYPE_DIR; - } - - // Is it excluded? - if( _syncEngine->excludedFiles().isExcluded(file, _syncEngine->localPath(), _syncEngine->ignoreHiddenFiles()) ) { - return SyncFileStatus(SyncFileStatus::StatusIgnore); - } - - // Error if it is in the selective sync blacklist - foreach(const auto &s, _syncEngine->journal()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList)) { - if (fileNameSlash.startsWith(s)) { - return SyncFileStatus(SyncFileStatus::StatusError); - } - } - - SyncFileStatus status(SyncFileStatus::StatusNone); - SyncJournalFileRecord rec = _syncEngine->journal()->getFileRecord(fileName ); - - if (estimateState(fileName, type, &status)) { - qDebug() << "Folder estimated status for" << fileName << "to" << status.toSocketAPIString(); - } else if (fileName == "") { - // sync folder itself - // FIXME: The new parent folder logic should take over this, treating the root the same as any folder. - } else if (type == CSYNC_FTW_TYPE_DIR) { - if (rec.isValid()) { - status.set(SyncFileStatus::StatusUpToDate); - } else { - qDebug() << "Could not determine state for folder" << fileName << "will set StatusSync"; - status.set(SyncFileStatus::StatusSync); - } - } else if (type == CSYNC_FTW_TYPE_FILE) { - if (rec.isValid()) { - if( FileSystem::getModTime(fi.absoluteFilePath()) == Utility::qDateTimeToTime_t(rec._modtime) ) { - status.set(SyncFileStatus::StatusUpToDate); - } else { - if (rec._remotePerm.isNull() || rec._remotePerm.contains("W") ) { - status.set(SyncFileStatus::StatusSync); - } else { - status.set(SyncFileStatus::StatusError); - } - } - } else { - qDebug() << "Could not determine state for file" << fileName << "will set StatusSync"; - status.set(SyncFileStatus::StatusSync); - } - } - - if (rec.isValid() && rec._remotePerm.contains("S")) - status.setSharedWithMe(true); - - // FIXME: Wrong, but will go away - if (status.tag() == SyncFileStatus::StatusSync) { - // check the parent folder if it is shared and if it is allowed to create a file/dir within - QDir d( fi.path() ); - auto parentPath = d.path(); - auto dirRec = _syncEngine->journal()->getFileRecord(parentPath); - bool isDir = type == CSYNC_FTW_TYPE_DIR; - while( !d.isRoot() && !(d.exists() && dirRec.isValid()) ) { - d.cdUp(); // returns true if the dir exists. - - parentPath = d.path(); - // cut the folder path - dirRec = _syncEngine->journal()->getFileRecord(parentPath); - - isDir = true; - } - if( dirRec.isValid() && !dirRec._remotePerm.isNull()) { - if( (isDir && !dirRec._remotePerm.contains("K")) - || (!isDir && !dirRec._remotePerm.contains("C")) ) { - status.set(SyncFileStatus::StatusError); - } - } - } - return status; -} - -void SyncFileStatusTracker::slotThreadTreeWalkResult(const SyncFileItemVector& items) -{ - addErroredSyncItemPathsToList(items, &_stateLastSyncItemsWithErrorNew); + // Must be a new file, wait for the filesystem watcher to trigger a sync + return SyncFileStatus(); } void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items) { - addErroredSyncItemPathsToList(items, &_stateLastSyncItemsWithErrorNew); -} + std::set oldProblems; + std::swap(_syncProblems, oldProblems); -void SyncFileStatusTracker::slotSyncFinished() -{ - _stateLastSyncItemsWithError = _stateLastSyncItemsWithErrorNew; - _stateLastSyncItemsWithErrorNew.clear(); + foreach (const SyncFileItemPtr &item, items) { + qDebug() << Q_FUNC_INFO << "Investigating" << item->destination() << item->_status; + + if (showErrorInSocketApi(*item)) + _syncProblems.insert({item->_file, SyncFileStatus::StatusError}); + else if (showWarningInSocketApi(*item)) + _syncProblems.insert({item->_file, SyncFileStatus::StatusWarning}); + + QString systemFileName = _syncEngine->localPath() + item->destination(); + // the trailing slash for directories must be appended as the filenames coming in + // from the plugins have that too. Otherwise the matching entry item is not found + // in the plugin. + if( item->_type == SyncFileItem::Type::Directory ) + systemFileName += QLatin1Char('/'); + emit fileStatusChanged(systemFileName, fileStatus(*item)); + } + + // Make sure to push any status that might have been resolved indirectly since the last sync + // (like an error file being deleted from disk) + for (auto it = _syncProblems.begin(); it != _syncProblems.end(); ++it) + oldProblems.erase(*it); + for (auto it = oldProblems.begin(); it != oldProblems.end(); ++it) { + if (it->severity == SyncFileStatus::StatusError) + invalidateParentPaths(it->path); + emit fileStatusChanged(_syncEngine->localPath() + it->path, fileStatus(it->path)); + } } void SyncFileStatusTracker::slotItemCompleted(const SyncFileItem &item) { + qDebug() << Q_FUNC_INFO << item.destination() << item._status; + if (showErrorInSocketApi(item)) { - _stateLastSyncItemsWithErrorNew.insert(item._file); + _syncProblems.insert({item._file, SyncFileStatus::StatusError}); + invalidateParentPaths(item.destination()); + } else if (showWarningInSocketApi(item)) { + _syncProblems.insert({item._file, SyncFileStatus::StatusWarning}); + } else { + // There is currently no situation where an error status set during discovery/update is fixed by propagation. + Q_ASSERT(_syncProblems.find(item._file) == _syncProblems.end()); } QString systemFileName = _syncEngine->localPath() + item.destination(); - // the trailing slash for directories must be appended as the filenames coming in // from the plugins have that too. Otherwise the matching entry item is not found // in the plugin. if( item._type == SyncFileItem::Type::Directory ) { systemFileName += QLatin1Char('/'); } - - auto status = fileStatus(item.destination()); - emit fileStatusChanged(systemFileName, status); + emit fileStatusChanged(systemFileName, fileStatus(item)); } -void SyncFileStatusTracker::slotItemDiscovered(const SyncFileItem &item) +SyncFileStatus SyncFileStatusTracker::fileStatus(const SyncFileItem& item) { - QString systemFileName = _syncEngine->localPath() + item.destination(); + // Hack to know if the item was taken from the sync engine (Sync), or from the database (UpToDate) + bool waitingForPropagation = item._direction != SyncFileItem::None && item._status == SyncFileItem::NoStatus; - // the trailing slash for directories must be appended as the filenames coming in - // from the plugins have that too. Otherwise the matching entry item is not found - // in the plugin. - if( item._type == SyncFileItem::Type::Directory ) { - systemFileName += QLatin1Char('/'); + SyncFileStatus status(SyncFileStatus::StatusUpToDate); + if (waitingForPropagation) { + status.set(SyncFileStatus::StatusSync); + } else if (showErrorInSocketApi(item)) { + status.set(SyncFileStatus::StatusError); + } else if (showWarningInSocketApi(item)) { + status.set(SyncFileStatus::StatusWarning); + } else { + // After a sync finished, we need to show the users issues from that last sync like the activity list does. + // Also used for parent directories showing a warning for an error child. + SyncFileStatus::SyncFileStatusTag problemStatus = lookupProblem(item.destination(), _syncProblems); + if (problemStatus != SyncFileStatus::StatusNone) + status.set(problemStatus); } - emit fileStatusChanged(systemFileName, SyncFileStatus(SyncFileStatus::StatusSync)); + if (item._remotePerm.contains("S")) + status.setSharedWithMe(true); + + return status; +} + +void SyncFileStatusTracker::invalidateParentPaths(const QString& path) +{ + QStringList splitPath = path.split('/', QString::SkipEmptyParts); + for (int i = 0; i < splitPath.size(); ++i) { + QString parentPath = splitPath.mid(0, i).join('/'); + emit fileStatusChanged(_syncEngine->localPath() + parentPath, fileStatus(parentPath)); + } } } diff --git a/src/libsync/syncfilestatustracker.h b/src/libsync/syncfilestatustracker.h index 4ed067ff18..d7ed3deae5 100644 --- a/src/libsync/syncfilestatustracker.h +++ b/src/libsync/syncfilestatustracker.h @@ -1,5 +1,6 @@ /* * Copyright (C) by Klaas Freitag + * Copyright (C) by Jocelyn Turcotte * * 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 @@ -17,38 +18,43 @@ #include "ownsql.h" #include "syncfileitem.h" #include "syncfilestatus.h" -#include -#include +#include namespace OCC { class SyncEngine; +struct Problem { + QString path; + SyncFileStatus::SyncFileStatusTag severity = SyncFileStatus::StatusError; + + Problem(const QString &path) : path(path) { } + Problem(const QString &path, SyncFileStatus::SyncFileStatusTag severity) : path(path), severity(severity) { } + + // Assume that each path will only have one severity, don't care about it in sorting + bool operator<(const Problem &other) const { return path < other.path; } + bool operator==(const Problem &other) const { return path == other.path; } +}; + class OWNCLOUDSYNC_EXPORT SyncFileStatusTracker : public QObject { Q_OBJECT public: - SyncFileStatusTracker(SyncEngine *syncEngine); + SyncFileStatusTracker(SyncEngine* syncEngine); SyncFileStatus fileStatus(const QString& systemFileName); signals: void fileStatusChanged(const QString& systemFileName, SyncFileStatus fileStatus); private slots: - void slotThreadTreeWalkResult(const SyncFileItemVector& items); void slotAboutToPropagate(SyncFileItemVector& items); - void slotSyncFinished(); - void slotItemCompleted(const SyncFileItem &item); - void slotItemDiscovered(const SyncFileItem &item); + void slotItemCompleted(const SyncFileItem& item); private: - bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s); - - SyncEngine *_syncEngine; - // SocketAPI: Cache files and folders that had errors so that they can - // get a red ERROR icon. - QSet _stateLastSyncItemsWithErrorNew; // gets moved to _stateLastSyncItemsWithError at end of sync - QSet _stateLastSyncItemsWithError; + SyncFileStatus fileStatus(const SyncFileItem& item); + void invalidateParentPaths(const QString& path); + SyncEngine* _syncEngine; + std::set _syncProblems; }; } diff --git a/src/libsync/utility.cpp b/src/libsync/utility.cpp index 6fca882323..5fed2f92fe 100644 --- a/src/libsync/utility.cpp +++ b/src/libsync/utility.cpp @@ -236,18 +236,6 @@ QString Utility::toCSyncScheme(const QString &urlStr) return url.toString(); } -bool Utility::doesSetContainPrefix(const QSet &l, const QString &p) { - - Q_FOREACH (const QString &setPath, l) { - //qDebug() << Q_FUNC_INFO << p << setPath << setPath.startsWith(p); - if (setPath.startsWith(p)) { - return true; - } - } - //qDebug() << "-> NOOOOO!!!" << p << l.count(); - return false; -} - QString Utility::escape(const QString &in) { #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) diff --git a/src/libsync/utility.h b/src/libsync/utility.h index 7aa31e3579..b8e276b10a 100644 --- a/src/libsync/utility.h +++ b/src/libsync/utility.h @@ -40,11 +40,6 @@ namespace Utility OWNCLOUDSYNC_EXPORT void setLaunchOnStartup(const QString &appName, const QString& guiName, bool launch); OWNCLOUDSYNC_EXPORT qint64 freeDiskSpace(const QString &path); OWNCLOUDSYNC_EXPORT QString toCSyncScheme(const QString &urlStr); - /** Like QLocale::toString(double, 'f', prec), but drops trailing zeros after the decimal point */ - - OWNCLOUDSYNC_EXPORT bool doesSetContainPrefix(const QSet &l, const QString &p); - - /** * @brief compactFormatDouble - formats a double value human readable. From 47a552f8c209a1e890ab14609e1d1f0c876e3965 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Tue, 29 Mar 2016 17:29:36 +0200 Subject: [PATCH 12/17] Use a std::map for SyncFileStatusTracker problems This prevents having to define a Problem structure with dubious operator overloads to accomplish the same. Also use std::map::lower_bound to quickly iterate over the list of problems. --- src/libsync/syncfilestatustracker.cpp | 42 +++++++++++++++++---------- src/libsync/syncfilestatustracker.h | 16 ++-------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index 5ae8b9692a..79dbf16f41 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -19,15 +19,25 @@ namespace OCC { -static SyncFileStatus::SyncFileStatusTag lookupProblem(const QString &pathToMatch, const std::set &set) +static SyncFileStatus::SyncFileStatusTag lookupProblem(const QString &pathToMatch, const std::map &problemMap) { - for (auto it = set.cbegin(); it != set.cend(); ++it) { - qDebug() << Q_FUNC_INFO << pathToMatch << it->severity << it->path; - auto problemPath = it->path; + auto lower = problemMap.lower_bound(pathToMatch); + for (auto it = lower; it != problemMap.cend(); ++it) { + const QString &problemPath = it->first; + SyncFileStatus::SyncFileStatusTag severity = it->second; + qDebug() << Q_FUNC_INFO << pathToMatch << severity << problemPath; if (problemPath == pathToMatch) - return it->severity; - else if (it->severity == SyncFileStatus::StatusError && problemPath.startsWith(pathToMatch)) + return severity; + else if (severity == SyncFileStatus::StatusError && problemPath.startsWith(pathToMatch)) return SyncFileStatus::StatusWarning; + else if (!problemPath.startsWith(pathToMatch)) + // Starting at lower_bound we get the first path that is not smaller, + // since: "/a/" < "/a/aa" < "/a/aa/aaa" < "/a/ab/aba" + // If problemMap keys are ["/a/aa/aaa", "/a/ab/aba"] and pathToMatch == "/a/aa", + // lower_bound(pathToMatch) will point to "/a/aa/aaa", and the moment that + // problemPath.startsWith(pathToMatch) == false, we know that we've looked + // at everything that interest us. + break; } return SyncFileStatus::StatusNone; } @@ -89,16 +99,16 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName) void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items) { - std::set oldProblems; + std::map oldProblems; std::swap(_syncProblems, oldProblems); foreach (const SyncFileItemPtr &item, items) { qDebug() << Q_FUNC_INFO << "Investigating" << item->destination() << item->_status; if (showErrorInSocketApi(*item)) - _syncProblems.insert({item->_file, SyncFileStatus::StatusError}); + _syncProblems[item->_file] = SyncFileStatus::StatusError; else if (showWarningInSocketApi(*item)) - _syncProblems.insert({item->_file, SyncFileStatus::StatusWarning}); + _syncProblems[item->_file] = SyncFileStatus::StatusWarning; QString systemFileName = _syncEngine->localPath() + item->destination(); // the trailing slash for directories must be appended as the filenames coming in @@ -112,11 +122,13 @@ void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items) // Make sure to push any status that might have been resolved indirectly since the last sync // (like an error file being deleted from disk) for (auto it = _syncProblems.begin(); it != _syncProblems.end(); ++it) - oldProblems.erase(*it); + oldProblems.erase(it->first); for (auto it = oldProblems.begin(); it != oldProblems.end(); ++it) { - if (it->severity == SyncFileStatus::StatusError) - invalidateParentPaths(it->path); - emit fileStatusChanged(_syncEngine->localPath() + it->path, fileStatus(it->path)); + const QString &path = it->first; + SyncFileStatus::SyncFileStatusTag severity = it->second; + if (severity == SyncFileStatus::StatusError) + invalidateParentPaths(path); + emit fileStatusChanged(_syncEngine->localPath() + path, fileStatus(path)); } } @@ -125,10 +137,10 @@ void SyncFileStatusTracker::slotItemCompleted(const SyncFileItem &item) qDebug() << Q_FUNC_INFO << item.destination() << item._status; if (showErrorInSocketApi(item)) { - _syncProblems.insert({item._file, SyncFileStatus::StatusError}); + _syncProblems[item._file] = SyncFileStatus::StatusError; invalidateParentPaths(item.destination()); } else if (showWarningInSocketApi(item)) { - _syncProblems.insert({item._file, SyncFileStatus::StatusWarning}); + _syncProblems[item._file] = SyncFileStatus::StatusWarning; } else { // There is currently no situation where an error status set during discovery/update is fixed by propagation. Q_ASSERT(_syncProblems.find(item._file) == _syncProblems.end()); diff --git a/src/libsync/syncfilestatustracker.h b/src/libsync/syncfilestatustracker.h index d7ed3deae5..cdf6133d94 100644 --- a/src/libsync/syncfilestatustracker.h +++ b/src/libsync/syncfilestatustracker.h @@ -18,24 +18,12 @@ #include "ownsql.h" #include "syncfileitem.h" #include "syncfilestatus.h" -#include +#include namespace OCC { class SyncEngine; -struct Problem { - QString path; - SyncFileStatus::SyncFileStatusTag severity = SyncFileStatus::StatusError; - - Problem(const QString &path) : path(path) { } - Problem(const QString &path, SyncFileStatus::SyncFileStatusTag severity) : path(path), severity(severity) { } - - // Assume that each path will only have one severity, don't care about it in sorting - bool operator<(const Problem &other) const { return path < other.path; } - bool operator==(const Problem &other) const { return path == other.path; } -}; - class OWNCLOUDSYNC_EXPORT SyncFileStatusTracker : public QObject { Q_OBJECT @@ -54,7 +42,7 @@ private: SyncFileStatus fileStatus(const SyncFileItem& item); void invalidateParentPaths(const QString& path); SyncEngine* _syncEngine; - std::set _syncProblems; + std::map _syncProblems; }; } From fbf23b6abbedc32ab99792ac96f44065990a1258 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Tue, 29 Mar 2016 17:39:30 +0200 Subject: [PATCH 13/17] Cleanup after the SyncFileStatusTracker change - Add missing explicit keywords - Add doc - Comment out verbose and partly redundant qDebug statements --- src/gui/socketapi.h | 2 +- src/libsync/syncfilestatustracker.cpp | 6 +++--- src/libsync/syncfilestatustracker.h | 7 ++++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index e3f1e481a9..763ca2616e 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -45,7 +45,7 @@ class SocketApi : public QObject Q_OBJECT public: - SocketApi(QObject* parent = 0); + explicit SocketApi(QObject* parent = 0); virtual ~SocketApi(); public slots: diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index 79dbf16f41..dd59a594a0 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -25,7 +25,7 @@ static SyncFileStatus::SyncFileStatusTag lookupProblem(const QString &pathToMatc for (auto it = lower; it != problemMap.cend(); ++it) { const QString &problemPath = it->first; SyncFileStatus::SyncFileStatusTag severity = it->second; - qDebug() << Q_FUNC_INFO << pathToMatch << severity << problemPath; + // qDebug() << Q_FUNC_INFO << pathToMatch << severity << problemPath; if (problemPath == pathToMatch) return severity; else if (severity == SyncFileStatus::StatusError && problemPath.startsWith(pathToMatch)) @@ -103,7 +103,7 @@ void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items) std::swap(_syncProblems, oldProblems); foreach (const SyncFileItemPtr &item, items) { - qDebug() << Q_FUNC_INFO << "Investigating" << item->destination() << item->_status; + // qDebug() << Q_FUNC_INFO << "Investigating" << item->destination() << item->_status; if (showErrorInSocketApi(*item)) _syncProblems[item->_file] = SyncFileStatus::StatusError; @@ -134,7 +134,7 @@ void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items) void SyncFileStatusTracker::slotItemCompleted(const SyncFileItem &item) { - qDebug() << Q_FUNC_INFO << item.destination() << item._status; + // qDebug() << Q_FUNC_INFO << item.destination() << item._status; if (showErrorInSocketApi(item)) { _syncProblems[item._file] = SyncFileStatus::StatusError; diff --git a/src/libsync/syncfilestatustracker.h b/src/libsync/syncfilestatustracker.h index cdf6133d94..a6ae1b3215 100644 --- a/src/libsync/syncfilestatustracker.h +++ b/src/libsync/syncfilestatustracker.h @@ -24,11 +24,16 @@ namespace OCC { class SyncEngine; +/** + * @brief Takes care of tracking the status of individual files as they + * go through the SyncEngine, to be reported as overlay icons in the shell. + * @ingroup libsync + */ class OWNCLOUDSYNC_EXPORT SyncFileStatusTracker : public QObject { Q_OBJECT public: - SyncFileStatusTracker(SyncEngine* syncEngine); + explicit SyncFileStatusTracker(SyncEngine* syncEngine); SyncFileStatus fileStatus(const QString& systemFileName); signals: From ef57d4ae1139a92cfc3e84c745d9d317cffe4971 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Tue, 29 Mar 2016 17:55:02 +0200 Subject: [PATCH 14/17] Move the SyncFileStatusTracker directory slash suffix logic in a method --- src/libsync/syncfilestatustracker.cpp | 29 +++++++++++++-------------- src/libsync/syncfilestatustracker.h | 2 ++ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index dd59a594a0..5b57975bcf 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -110,13 +110,7 @@ void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items) else if (showWarningInSocketApi(*item)) _syncProblems[item->_file] = SyncFileStatus::StatusWarning; - QString systemFileName = _syncEngine->localPath() + item->destination(); - // the trailing slash for directories must be appended as the filenames coming in - // from the plugins have that too. Otherwise the matching entry item is not found - // in the plugin. - if( item->_type == SyncFileItem::Type::Directory ) - systemFileName += QLatin1Char('/'); - emit fileStatusChanged(systemFileName, fileStatus(*item)); + emit fileStatusChanged(getSystemDestination(*item), fileStatus(*item)); } // Make sure to push any status that might have been resolved indirectly since the last sync @@ -146,14 +140,7 @@ void SyncFileStatusTracker::slotItemCompleted(const SyncFileItem &item) Q_ASSERT(_syncProblems.find(item._file) == _syncProblems.end()); } - QString systemFileName = _syncEngine->localPath() + item.destination(); - // the trailing slash for directories must be appended as the filenames coming in - // from the plugins have that too. Otherwise the matching entry item is not found - // in the plugin. - if( item._type == SyncFileItem::Type::Directory ) { - systemFileName += QLatin1Char('/'); - } - emit fileStatusChanged(systemFileName, fileStatus(item)); + emit fileStatusChanged(getSystemDestination(item), fileStatus(item)); } SyncFileStatus SyncFileStatusTracker::fileStatus(const SyncFileItem& item) @@ -191,4 +178,16 @@ void SyncFileStatusTracker::invalidateParentPaths(const QString& path) } } +QString SyncFileStatusTracker::getSystemDestination(const SyncFileItem& item) +{ + QString systemFileName = _syncEngine->localPath() + item.destination(); + // the trailing slash for directories must be appended as the filenames coming in + // from the plugins have that too. Otherwise the matching entry item is not found + // in the plugin. + if( item._type == SyncFileItem::Type::Directory ) { + systemFileName += QLatin1Char('/'); + } + return systemFileName; +} + } diff --git a/src/libsync/syncfilestatustracker.h b/src/libsync/syncfilestatustracker.h index a6ae1b3215..e8e398415b 100644 --- a/src/libsync/syncfilestatustracker.h +++ b/src/libsync/syncfilestatustracker.h @@ -46,6 +46,8 @@ private slots: private: SyncFileStatus fileStatus(const SyncFileItem& item); void invalidateParentPaths(const QString& path); + QString getSystemDestination(const SyncFileItem& syncEnginePath); + SyncEngine* _syncEngine; std::map _syncProblems; }; From 56064c9366e06d128badc273f5a1c270035aa7e3 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Wed, 30 Mar 2016 11:22:01 +0200 Subject: [PATCH 15/17] Fix sibbling directories showing up as warning Looking up a/aa while an error is present in a/aab/aaba would return a warning status since a/aa is a substring of a/aab. Fix the issue by checking if the following character is a slash. --- src/libsync/syncfilestatustracker.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index 5b57975bcf..61ebcfb186 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -26,11 +26,12 @@ static SyncFileStatus::SyncFileStatusTag lookupProblem(const QString &pathToMatc const QString &problemPath = it->first; SyncFileStatus::SyncFileStatusTag severity = it->second; // qDebug() << Q_FUNC_INFO << pathToMatch << severity << problemPath; - if (problemPath == pathToMatch) + if (problemPath == pathToMatch) { return severity; - else if (severity == SyncFileStatus::StatusError && problemPath.startsWith(pathToMatch)) + } else if (severity == SyncFileStatus::StatusError && problemPath.startsWith(pathToMatch) && problemPath.at(pathToMatch.size()) == '/') { + Q_ASSERT(!pathToMatch.endsWith('/')); return SyncFileStatus::StatusWarning; - else if (!problemPath.startsWith(pathToMatch)) + } else if (!problemPath.startsWith(pathToMatch)) { // Starting at lower_bound we get the first path that is not smaller, // since: "/a/" < "/a/aa" < "/a/aa/aaa" < "/a/ab/aba" // If problemMap keys are ["/a/aa/aaa", "/a/ab/aba"] and pathToMatch == "/a/aa", @@ -38,6 +39,7 @@ static SyncFileStatus::SyncFileStatusTag lookupProblem(const QString &pathToMatc // problemPath.startsWith(pathToMatch) == false, we know that we've looked // at everything that interest us. break; + } } return SyncFileStatus::StatusNone; } From 2c0caf8b75ea048c942bc7507027ff9f26bffcd7 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Wed, 30 Mar 2016 12:10:51 +0200 Subject: [PATCH 16/17] Fix the SocketApi status of CSYNC_FILE_SILENTLY_EXCLUDED files Bring back the hardcoded status logic for excluded files. Since the activity log doesn't even mention those files on purpose, we can't rely on the SyncEngine to notify us about the status of those files. --- src/libsync/syncfilestatustracker.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index 61ebcfb186..72076c923c 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -86,6 +86,15 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName) qDebug() << "Removed trailing slash: " << fileName; } + // The SyncEngine won't notify us at all for CSYNC_FILE_SILENTLY_EXCLUDED + // and CSYNC_FILE_EXCLUDE_AND_REMOVE excludes. Even though it's possible + // that the status of CSYNC_FILE_EXCLUDE_LIST excludes will change if the user + // update the exclude list at runtime and doing it statically here removes + // our ability to notify changes through the fileStatusChanged signal, + // it's an acceptable compromize to treat all exclude types the same. + if( _syncEngine->excludedFiles().isExcluded(_syncEngine->localPath() + fileName, _syncEngine->localPath(), _syncEngine->ignoreHiddenFiles()) ) + return SyncFileStatus(SyncFileStatus::StatusWarning); + SyncFileItem* item = _syncEngine->findSyncItem(fileName); if (item) return fileStatus(*item); From a0260c29c013ac898b56240ee37b8cd3a64bc358 Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Wed, 30 Mar 2016 12:19:09 +0200 Subject: [PATCH 17/17] Fix the build on Windows --- src/gui/owncloudgui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 944b8ac407..384ddba2fb 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -35,6 +35,7 @@ #include "creds/abstractcredentials.h" #include +#include #include #include