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/accountsettings.cpp b/src/gui/accountsettings.cpp index 0dea852ff2..e1b5469d8b 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -35,6 +35,7 @@ #include #include +#include #include #include #include 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..c00b95da6b 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) @@ -70,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(); @@ -84,24 +88,37 @@ 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(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(newBigFolder(QString)), this, SLOT(slotNewBigFolderDiscovered(QString))); } Folder::~Folder() { - if( _engine ) { - _engine->abort(); - _engine.reset(0); - } -} - -void Folder::setAccountState( AccountState *account ) -{ - _accountState = account; -} - -AccountState* Folder::accountState() const -{ - return _accountState; } void Folder::checkLocalPath() @@ -192,7 +209,7 @@ QString Folder::cleanPath() bool Folder::isBusy() const { - return !_engine.isNull(); + return _engine->isSyncRunning(); } QString Folder::remotePath() const @@ -202,9 +219,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 +270,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 +337,7 @@ void Folder::etagRetreived(const QString& etag) emit scheduleToSync(this); } - if( _accountState ) { - _accountState->tagLastSuccessfullETagRequest(); - } + _accountState->tagLastSuccessfullETagRequest(); } void Folder::etagRetreivedFromSyncEngine(const QString& etag) @@ -359,7 +366,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(); @@ -434,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) { @@ -468,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() ) { @@ -557,7 +560,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; } @@ -585,91 +588,14 @@ 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) -{ - // 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); -} - - -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.isNull()) { - if (_engine->estimateState(fn, t, 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 - if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) { - s->set(SyncFileStatus::STATUS_ERROR); - return true; - } - // If sync running: _syncedItems -> SyncingState - if (!_engine.isNull()) { - if (_engine->estimateState(fn, t, s)) { - return true; - } - } - } - return false; -} 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 +606,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,56 +613,19 @@ 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 _engine->excludedFiles().isExcluded(fullPath, path(), _definition.ignoreHiddenFiles); } bool Folder::isFileExcludedRelative(const QString& relativePath) const { - return isFileExcludedAbsolute(path() + relativePath); + 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()) { - 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!"; - if( _engine ) { + if( _engine->isSyncRunning() ) { _engine->abort(); // Do not display an error message, user knows his own actions. @@ -814,11 +698,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); @@ -843,10 +722,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")); @@ -854,32 +729,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; @@ -896,27 +745,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); } @@ -955,20 +801,11 @@ void Folder::slotSyncFinished(bool success) } bubbleUpSyncResult(); - bool anotherSyncNeeded = false; - if (_engine) { - anotherSyncNeeded = _engine->isAnotherSyncNeeded(); - _engine.reset(0); - } + bool anotherSyncNeeded = _engine->isAnotherSyncNeeded(); // _watcher->setEventsEnabledDelayed(2000); - // 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); qDebug() << " ** error Strings: " << _errors; @@ -1064,10 +901,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); @@ -1075,11 +908,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 cb13e8d8c1..55035c491a 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -21,21 +21,13 @@ #include "progressdispatcher.h" #include "syncjournaldb.h" #include "clientproxy.h" -#include "syncfilestatus.h" #include "networkjobs.h" #include -#include -#include -#include #include #include -#include -#include -#include - class QThread; class QSettings; @@ -87,7 +79,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 +89,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 @@ -182,8 +173,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(); } @@ -257,18 +247,15 @@ 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 &); void etagRetreivedFromSyncEngine(const QString &); - void slotAboutToPropagate(SyncFileItemVector& ); void slotThreadTreeWalkResult(const SyncFileItemVector& ); // after sync is done void slotEmitFinishedDelayed(); - void watcherSlot(QString); void slotNewBigFolderDiscovered(const QString &); private: @@ -278,10 +265,19 @@ 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 ); - QPointer _accountState; + AccountStatePtr _accountState; FolderDefinition _definition; SyncResult _syncResult; @@ -306,15 +302,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/folderman.cpp b/src/gui/folderman.cpp index 85bb94a47f..006555f3c8 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()); @@ -108,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() @@ -147,15 +146,10 @@ 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 - if( _socketApi ) { - _socketApi->slotRegisterPath(folder->alias()); - } + _socketApi->slotRegisterPath(folder->alias()); } void FolderMan::addMonitorPath( const QString& alias, const QString& path ) @@ -206,9 +200,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); } @@ -307,7 +300,7 @@ QString FolderMan::escapeAlias( const QString& alias ) SocketApi *FolderMan::socketApi() { - return this->_socketApi; + return this->_socketApi.data(); } QString FolderMan::unescapeAlias( const QString& alias ) @@ -395,10 +388,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 @@ -478,21 +469,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(); @@ -786,9 +773,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 +782,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; @@ -812,6 +798,8 @@ Folder* FolderMan::addFolderInternal(const FolderDefinition& folderDefinition) 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; @@ -935,15 +923,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 c8fe41374a..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" @@ -202,7 +201,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 * ); @@ -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/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 39796bbb57..4f38015a6b 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -35,6 +35,7 @@ #include "creds/abstractcredentials.h" #include +#include #include #include @@ -350,7 +351,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/gui/socketapi.cpp b/src/gui/socketapi.cpp index 051041b18f..754291e804 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" @@ -113,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() @@ -195,24 +192,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) @@ -231,7 +212,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 { @@ -240,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 = this->fileStatus(f, 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; @@ -346,7 +290,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(); } @@ -383,11 +327,10 @@ 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 && - fileStatus.tag() != SyncFileStatus::STATUS_UPDATED) { + if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) { const QString message = QLatin1String("SHARE:NOTSYNCED:")+QDir::toNativeSeparators(localFile); sendMessage(socket, message); return; @@ -402,7 +345,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() ) { @@ -439,11 +382,10 @@ 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 && - fileStatus.tag() != SyncFileStatus::STATUS_UPDATED) { + if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) { const QString message = QLatin1String("SHARE_STATUS:NOTSYNCED:")+QDir::toNativeSeparators(localFile); sendMessage(socket, message); return; @@ -494,227 +436,4 @@ 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. - */ -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 = dbFileRecord_capi(folder, 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()) { - 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 (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 = dbFileRecord_capi(folder, 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); - - 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/gui/socketapi.h b/src/gui/socketapi.h index f17660e4a5..763ca2616e 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -17,7 +17,7 @@ #define SOCKETAPI_H #include "syncfileitem.h" -#include "syncjournalfilerecord.h" +#include "syncfilestatus.h" #include "ownsql.h" #if defined(Q_OS_MAC) @@ -45,7 +45,7 @@ class SocketApi : public QObject Q_OBJECT public: - SocketApi(QObject* parent); + explicit SocketApi(QObject* parent = 0); virtual ~SocketApi(); public slots: @@ -61,14 +61,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); - 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 +79,6 @@ private: QList _listeners; SocketApiServer _localServer; - QHash> _dbQueries; - QHash> _openDbs; }; } 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/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..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; @@ -62,16 +58,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 +79,11 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::isExcluded( if (fi.isDir()) { type = CSYNC_FTW_TYPE_DIR; } - QReadLocker lock(&_mutex); - return csync_excluded_no_ctx(*_excludesPtr, relativePath.toUtf8(), type); + + QString relativePath = filePath.mid(basePath.size()); + if (relativePath.endsWith(QLatin1Char('/'))) { + relativePath.chop(1); + } + + 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..5e47f7c2d8 100644 --- a/src/libsync/excludedfiles.h +++ b/src/libsync/excludedfiles.h @@ -16,7 +16,6 @@ #include "owncloudlib.h" #include -#include #include extern "C" { @@ -49,14 +48,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: @@ -70,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 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/syncengine.cpp b/src/libsync/syncengine.cpp index 7eb50550e4..a7f75b422a 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) @@ -92,13 +93,14 @@ 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"); - _thread.start(); } SyncEngine::~SyncEngine() { + abort(); _excludedFiles.reset(); csync_destroy(_csync_ctx); _thread.quit(); @@ -662,10 +664,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 +744,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 +1004,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); @@ -1301,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::STATUS_ERROR); - else if (item->_status == SyncFileItem::FileIgnored) - s->set(SyncFileStatus::STATUS_IGNORE); - else if (item->_status == SyncFileItem::Success) - s->set(SyncFileStatus::STATUS_UPDATED); - else - s->set(SyncFileStatus::STATUS_EVAL); - 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 7d24e89621..934e0728c7 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" @@ -69,19 +69,23 @@ 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 */ 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; } - 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. * @@ -91,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 @@ -168,7 +172,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 +184,7 @@ private: AccountPtr _account; CSYNC *_csync_ctx; bool _needsUpdate; + bool _syncRunning; QString _localPath; QUrl _remoteUrl; QString _remotePath; @@ -208,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/syncfilestatus.cpp b/src/libsync/syncfilestatus.cpp index 313be6d517..2afd5c0995 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,22 @@ 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 StatusWarning: + // The protocol says IGNORE, but all implementations show a yellow warning sign. 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..fddcf1af0b 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, + StatusWarning, + StatusUpToDate, + StatusError, }; SyncFileStatus(); diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp new file mode 100644 index 0000000000..72076c923c --- /dev/null +++ b/src/libsync/syncfilestatustracker.cpp @@ -0,0 +1,204 @@ +/* + * 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 + * 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 "syncengine.h" +#include "syncjournaldb.h" +#include "syncjournalfilerecord.h" + +namespace OCC { + +static SyncFileStatus::SyncFileStatusTag lookupProblem(const QString &pathToMatch, const std::map &problemMap) +{ + 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 severity; + } else if (severity == SyncFileStatus::StatusError && problemPath.startsWith(pathToMatch) && problemPath.at(pathToMatch.size()) == '/') { + Q_ASSERT(!pathToMatch.endsWith('/')); + 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; +} + +/** + * 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 inline bool showErrorInSocketApi(const SyncFileItem& item) +{ + const auto status = item._status; + return status == SyncFileItem::NormalError + || status == SyncFileItem::FatalError + || item._hasBlacklistEntry; +} + +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(aboutToPropagate(SyncFileItemVector&)), + this, SLOT(slotAboutToPropagate(SyncFileItemVector&))); + connect(syncEngine, SIGNAL(itemCompleted(const SyncFileItem&, const PropagatorJob&)), + this, SLOT(slotItemCompleted(const SyncFileItem&))); +} + + +SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName) +{ + QString fileName = systemFileName.normalized(QString::NormalizationForm_C); + if( fileName.endsWith(QLatin1Char('/')) ) { + fileName.truncate(fileName.length()-1); + 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); + + // 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()); + + // Must be a new file, wait for the filesystem watcher to trigger a sync + return SyncFileStatus(); +} + +void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items) +{ + 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[item->_file] = SyncFileStatus::StatusError; + else if (showWarningInSocketApi(*item)) + _syncProblems[item->_file] = SyncFileStatus::StatusWarning; + + emit fileStatusChanged(getSystemDestination(*item), 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->first); + for (auto it = oldProblems.begin(); it != oldProblems.end(); ++it) { + const QString &path = it->first; + SyncFileStatus::SyncFileStatusTag severity = it->second; + if (severity == SyncFileStatus::StatusError) + invalidateParentPaths(path); + emit fileStatusChanged(_syncEngine->localPath() + path, fileStatus(path)); + } +} + +void SyncFileStatusTracker::slotItemCompleted(const SyncFileItem &item) +{ + // qDebug() << Q_FUNC_INFO << item.destination() << item._status; + + if (showErrorInSocketApi(item)) { + _syncProblems[item._file] = SyncFileStatus::StatusError; + invalidateParentPaths(item.destination()); + } else if (showWarningInSocketApi(item)) { + _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()); + } + + emit fileStatusChanged(getSystemDestination(item), fileStatus(item)); +} + +SyncFileStatus SyncFileStatusTracker::fileStatus(const SyncFileItem& item) +{ + // 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; + + 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); + } + + 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)); + } +} + +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 new file mode 100644 index 0000000000..e8e398415b --- /dev/null +++ b/src/libsync/syncfilestatustracker.h @@ -0,0 +1,57 @@ +/* + * 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 + * 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 + +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: + explicit SyncFileStatusTracker(SyncEngine* syncEngine); + SyncFileStatus fileStatus(const QString& systemFileName); + +signals: + void fileStatusChanged(const QString& systemFileName, SyncFileStatus fileStatus); + +private slots: + void slotAboutToPropagate(SyncFileItemVector& items); + void slotItemCompleted(const SyncFileItem& item); + +private: + SyncFileStatus fileStatus(const SyncFileItem& item); + void invalidateParentPaths(const QString& path); + QString getSystemDestination(const SyncFileItem& syncEnginePath); + + SyncEngine* _syncEngine; + std::map _syncProblems; +}; + +} + +#endif diff --git a/src/libsync/utility.cpp b/src/libsync/utility.cpp index bfeafab8a2..cffdd54e79 100644 --- a/src/libsync/utility.cpp +++ b/src/libsync/utility.cpp @@ -247,18 +247,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 032a3e7979..044988fe16 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. 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