diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d802c2b50b1..d297e3eb96f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,7 +48,8 @@ jobs: useSonarCloud: testOptions: name: ${{ matrix.name }} - + #let the other builds run even if one fails + continue-on-error: true runs-on: ${{ matrix.os }} env: diff --git a/src/common/filesystembase.cpp b/src/common/filesystembase.cpp index 29fb9d3d133..841388c94c7 100644 --- a/src/common/filesystembase.cpp +++ b/src/common/filesystembase.cpp @@ -141,16 +141,7 @@ void FileSystem::setFileReadOnly(const QString &filename, bool readonly) file.setPermissions(permissions); } -void FileSystem::setFolderMinimumPermissions(const QString &filename) -{ -#ifdef Q_OS_MAC - QFile::Permissions perm = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; - QFile file(filename); - file.setPermissions(perm); -#else - Q_UNUSED(filename); -#endif -} +void FileSystem::setFolderMinimumPermissions(const QString &filename) { } void FileSystem::setFileReadOnlyWeak(const QString &filename, bool readonly) diff --git a/src/common/filesystembase.h b/src/common/filesystembase.h index 76eaeb4b995..f5556f25e2c 100644 --- a/src/common/filesystembase.h +++ b/src/common/filesystembase.h @@ -54,8 +54,7 @@ namespace FileSystem { * List of characters not allowd in filenames on Windows */ constexpr_list auto IllegalFilenameCharsWindows = { - QLatin1Char('\\'), QLatin1Char(':'), QLatin1Char('?'), QLatin1Char('*'), QLatin1Char('"'), QLatin1Char('>'), QLatin1Char('<'), QLatin1Char('|') - }; + QLatin1Char('\\'), QLatin1Char(':'), QLatin1Char('?'), QLatin1Char('*'), QLatin1Char('"'), QLatin1Char('>'), QLatin1Char('<'), QLatin1Char('|')}; /** * @brief Mark the file as hidden (only has effects on windows) diff --git a/src/common/syncjournaldb.h b/src/common/syncjournaldb.h index 858107c23bb..2a1f1bac33e 100644 --- a/src/common/syncjournaldb.h +++ b/src/common/syncjournaldb.h @@ -46,7 +46,7 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject { Q_OBJECT public: - explicit SyncJournalDb(const QString &dbFilePath, QObject *parent = nullptr); + explicit SyncJournalDb(const QString &dbFilePath, QObject *parent); ~SyncJournalDb() override; /// Create a journal path for a specific configuration diff --git a/src/common/vfs.cpp b/src/common/vfs.cpp index f185942e261..1d759309bda 100644 --- a/src/common/vfs.cpp +++ b/src/common/vfs.cpp @@ -236,7 +236,7 @@ Vfs::Mode OCC::VfsPluginManager::bestAvailableVfsMode() const return Vfs::Off; } -std::unique_ptr OCC::VfsPluginManager::createVfsFromPlugin(Vfs::Mode mode) const +Vfs *OCC::VfsPluginManager::createVfsFromPlugin(Vfs::Mode mode, QObject *parent) const { auto name = Utility::enumToString(mode); if (name.isEmpty()) @@ -261,7 +261,7 @@ std::unique_ptr OCC::VfsPluginManager::createVfsFromPlugin(Vfs::Mode mode) return nullptr; } - auto vfs = std::unique_ptr(qobject_cast(factory->create(nullptr))); + Vfs *vfs = qobject_cast(factory->create(parent)); if (!vfs) { qCCritical(lcPlugin) << "Plugin" << loader.fileName() << "does not create a Vfs instance"; return nullptr; diff --git a/src/common/vfs.h b/src/common/vfs.h index 597177555fd..168408420cb 100644 --- a/src/common/vfs.h +++ b/src/common/vfs.h @@ -81,10 +81,13 @@ struct OCSYNC_EXPORT VfsSetupParams return _baseUrl; } + // sync engine is only used by win vfs impl SyncEngine *syncEngine() const; private: QUrl _baseUrl; + // this should be a QPointer but when I try to make it so, I get compile errors because std::isConvertible fails + // no freaking idea but I'll figure it out later SyncEngine *_syncEngine; }; @@ -141,7 +144,7 @@ class OCSYNC_EXPORT Vfs : public QObject using AvailabilityResult = Result; public: - explicit Vfs(QObject* parent = nullptr); + explicit Vfs(QObject *parent); ~Vfs() override; virtual Mode mode() const = 0; @@ -302,7 +305,7 @@ class OCSYNC_EXPORT VfsPluginManager Vfs::Mode bestAvailableVfsMode() const; /// Create a VFS instance for the mode, returns nullptr on failure. - std::unique_ptr createVfsFromPlugin(Vfs::Mode mode) const; + Vfs *createVfsFromPlugin(Vfs::Mode mode, QObject *parent) const; static const VfsPluginManager &instance(); diff --git a/src/csync/csync_exclude.cpp b/src/csync/csync_exclude.cpp index d2d06a0da70..e7f7d7fb138 100644 --- a/src/csync/csync_exclude.cpp +++ b/src/csync/csync_exclude.cpp @@ -220,8 +220,9 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(QStringView path) using namespace OCC; -ExcludedFiles::ExcludedFiles() - : _clientVersion(OCC::Version::version()) +ExcludedFiles::ExcludedFiles(QObject *parent) + : QObject(parent) + , _clientVersion(OCC::Version::version()) { // Windows used to use PathMatchSpec which allows *foo to match abc/deffoo. _wildcardsMatchSlash = Utility::isWindows(); diff --git a/src/csync/csync_exclude.h b/src/csync/csync_exclude.h index 241763833c2..15c5b51e99b 100644 --- a/src/csync/csync_exclude.h +++ b/src/csync/csync_exclude.h @@ -66,7 +66,9 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject { Q_OBJECT public: - ExcludedFiles(); + // need to allow nullptr as this is also used in the selective sync routine as a straight object instance, so no parent + // is needed + ExcludedFiles(QObject *parent = nullptr); ~ExcludedFiles() override; /** diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 77fb3c3129c..3a3a8742ddc 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -118,6 +118,7 @@ add_subdirectory(newaccountwizard) add_subdirectory(socketapi) add_subdirectory(spaces) add_subdirectory(FoldersGui) +add_subdirectory(foldermanagement) target_include_directories(owncloudGui PUBLIC ${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index d562fbd7f02..61029c266fe 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -28,6 +28,7 @@ #include "configfile.h" #include "filesystem.h" #include "folderman.h" +#include "foldermanagement/foldermanagementutils.h" #include "folderwatcher.h" #include "libsync/graphapi/spacesmanager.h" #include "localdiscoverytracker.h" @@ -91,14 +92,26 @@ using namespace FileSystem::SizeLiterals; Q_LOGGING_CATEGORY(lcFolder, "gui.folder", QtInfoMsg) -Folder::Folder(const FolderDefinition &definition, AccountState *accountState, std::unique_ptr &&vfs, bool ignoreHiddenFiles, QObject *parent) +Folder::Folder(const FolderDefinition &definition, AccountState *accountState, SyncJournalDb *journal, Vfs *vfs, bool ignoreHiddenFiles, QObject *parent) : QObject(parent) , _accountState(accountState) , _definition(definition) - , _journal(_definition.absoluteJournalPath()) , _fileLog(new SyncRunFileLog) - , _vfs(vfs.release()) { + journal->setParent(this); + _journal = journal; + + // this is temporary! we only clear the parent on the vfs pointer because for the time being, we are wrapping this in a shared pointer + // the shared pointer is marked for death, then we will be able to set the vfs parent to this, as it should be + vfs->setParent(this); + _vfs = vfs; + + // the FolderBuilder should fail if the account state or account are dead, so this should never trigger + Q_ASSERT(_accountState && _accountState->account()); + // FolderBuilder should also fail if it could not build the other dependencies so again, assert should never trigger + Q_ASSERT(_vfs); + Q_ASSERT(_journal); + _timeSinceLastSyncStart.start(); _timeSinceLastSyncDone.start(); @@ -107,60 +120,53 @@ Folder::Folder(const FolderDefinition &definition, AccountState *accountState, s status = SyncResult::Paused; } setSyncState(status); - // check if the starting conditions are legit - if (_accountState && _accountState->account() && checkLocalPath()) { - prepareFolder(path()); - // those errors should not persist over sessions - _journal.wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::Category::LocalSoftError); - // todo: the engine needs to be created externally, presumably by the folderman, and passed in by injection - // current impl can result in an invalid engine which is just a mess given the folder is useless without it - _engine.reset(new SyncEngine(_accountState->account(), webDavUrl(), path(), remotePath(), &_journal)); - // pass the setting if hidden files are to be ignored, will be read in csync_update - _engine->setIgnoreHiddenFiles(ignoreHiddenFiles); - - if (!_engine->loadDefaultExcludes()) { - qCWarning(lcFolder, "Could not read system exclude file"); - } - connect(_accountState, &AccountState::isConnectedChanged, this, &Folder::canSyncChanged); + // todo: the engine needs to be created externally, presumably by the folderman, and passed in by injection + // current impl can result in an invalid engine which is just a mess given the folder is useless without it + _engine = new SyncEngine(_accountState->account(), webDavUrl(), path(), remotePath(), _journal, this); + // pass the setting if hidden files are to be ignored, will be read in csync_update + _engine->setIgnoreHiddenFiles(ignoreHiddenFiles); + // consider passing this in instead of hitting the config file again + ConfigFile cfgFile; + _engine->setMoveToTrash(cfgFile.moveToTrash()); - connect(_engine.data(), &SyncEngine::started, this, &Folder::slotSyncStarted, Qt::QueuedConnection); - connect(_engine.data(), &SyncEngine::finished, this, &Folder::slotSyncFinished, Qt::QueuedConnection); + if (!_engine->loadDefaultExcludes()) { + qCWarning(lcFolder, "Could not read system exclude file"); + } - connect(_engine.data(), &SyncEngine::transmissionProgress, this, - [this](const ProgressInfo &pi) { Q_EMIT ProgressDispatcher::instance()->progressInfo(this, pi); }); + connect(_accountState, &AccountState::isConnectedChanged, this, &Folder::canSyncChanged); - connect(_engine.data(), &SyncEngine::transmissionProgress, this, &Folder::progressUpdate); + connect(_engine, &SyncEngine::started, this, &Folder::slotSyncStarted, Qt::QueuedConnection); + connect(_engine, &SyncEngine::finished, this, &Folder::slotSyncFinished, Qt::QueuedConnection); - connect(_engine.data(), &SyncEngine::itemCompleted, this, &Folder::slotItemCompleted); - connect(_engine.data(), &SyncEngine::seenLockedFile, FolderMan::instance(), &FolderMan::slotSyncOnceFileUnlocks); - connect(_engine.data(), &SyncEngine::aboutToPropagate, - this, &Folder::slotLogPropagationStart); - connect(_engine.data(), &SyncEngine::syncError, this, &Folder::slotSyncError); + connect( + _engine, &SyncEngine::transmissionProgress, this, [this](const ProgressInfo &pi) { Q_EMIT ProgressDispatcher::instance()->progressInfo(this, pi); }); - connect(ProgressDispatcher::instance(), &ProgressDispatcher::folderConflicts, - this, &Folder::slotFolderConflicts); - connect(_engine.data(), &SyncEngine::excluded, this, [this](const QString &path) { Q_EMIT ProgressDispatcher::instance()->excluded(this, path); }); + connect(_engine, &SyncEngine::transmissionProgress, this, &Folder::progressUpdate); - _localDiscoveryTracker.reset(new LocalDiscoveryTracker); - connect(_engine.data(), &SyncEngine::finished, - _localDiscoveryTracker.data(), &LocalDiscoveryTracker::slotSyncFinished); - connect(_engine.data(), &SyncEngine::itemCompleted, - _localDiscoveryTracker.data(), &LocalDiscoveryTracker::slotItemCompleted); + connect(_engine, &SyncEngine::itemCompleted, this, &Folder::slotItemCompleted); + connect(_engine, &SyncEngine::seenLockedFile, FolderMan::instance(), &FolderMan::slotSyncOnceFileUnlocks); + connect(_engine, &SyncEngine::aboutToPropagate, this, &Folder::slotLogPropagationStart); + connect(_engine, &SyncEngine::syncError, this, &Folder::slotSyncError); - connect(_accountState->account()->spacesManager(), &GraphApi::SpacesManager::spaceChanged, this, [this](GraphApi::Space *changedSpace) { - if (_definition.spaceId() == changedSpace->id()) { - emit spaceChanged(); - } - }); - if (space()) + connect(ProgressDispatcher::instance(), &ProgressDispatcher::folderConflicts, this, &Folder::slotFolderConflicts); + connect(_engine, &SyncEngine::excluded, this, [this](const QString &path) { Q_EMIT ProgressDispatcher::instance()->excluded(this, path); }); + + _localDiscoveryTracker = new LocalDiscoveryTracker(this); + connect(_engine, &SyncEngine::finished, _localDiscoveryTracker, &LocalDiscoveryTracker::slotSyncFinished); + connect(_engine, &SyncEngine::itemCompleted, _localDiscoveryTracker, &LocalDiscoveryTracker::slotItemCompleted); + + connect(_accountState->account()->spacesManager(), &GraphApi::SpacesManager::spaceChanged, this, [this](GraphApi::Space *changedSpace) { + if (_definition.spaceId() == changedSpace->id()) { emit spaceChanged(); + } + }); - // Potentially upgrade suffix vfs to windows vfs - OC_ENFORCE(_vfs); - // Initialize the vfs plugin. Do this after the UI is running, so we can show a dialog when something goes wrong. - QTimer::singleShot(0, this, &Folder::startVfs); - } + if (space()) + emit spaceChanged(); + + // Initialize the vfs plugin. Do this after the UI is running, so we can show a dialog when something goes wrong. + QTimer::singleShot(0, this, &Folder::startVfs); } Folder::~Folder() @@ -170,21 +176,8 @@ Folder::~Folder() _vfs->stop(); // Reset then engine first as it will abort and try to access members of the Folder - _engine.reset(); -} - -Result Folder::checkPathLength(const QString &path) -{ -#ifdef Q_OS_WIN - if (path.size() > MAX_PATH) { - if (!FileSystem::longPathsEnabledOnWindows()) { - return tr("The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder.").arg(path); - } - } -#else - Q_UNUSED(path) -#endif - return {}; + // todo: follow up on that comment above. The engine abort should NOT be accessing anything that is not directly in its own realm! + // _engine.reset(); } GraphApi::Space *Folder::space() const @@ -195,106 +188,22 @@ GraphApi::Space *Folder::space() const return nullptr; } -bool Folder::checkLocalPath() -{ -#ifdef Q_OS_WIN - QNtfsPermissionCheckGuard ntfs_perm; -#endif - const QFileInfo fi(_definition.localPath()); - _canonicalLocalPath = fi.canonicalFilePath(); -#ifdef Q_OS_MAC - // Workaround QTBUG-55896 (Should be fixed in Qt 5.8) - _canonicalLocalPath = _canonicalLocalPath.normalized(QString::NormalizationForm_C); -#endif - if (_canonicalLocalPath.isEmpty()) { - qCWarning(lcFolder) << "Broken symlink:" << _definition.localPath(); - _canonicalLocalPath = _definition.localPath(); - } else if (!_canonicalLocalPath.endsWith(QLatin1Char('/'))) { - _canonicalLocalPath.append(QLatin1Char('/')); - } - - QString error; - if (fi.isDir() && fi.isReadable() && fi.isWritable()) { - auto pathLengthCheck = checkPathLength(_canonicalLocalPath); - if (!pathLengthCheck) { - error = pathLengthCheck.error(); - } - - if (error.isEmpty()) { - qCDebug(lcFolder) << "Checked local path ok"; - if (!_journal.open()) { - error = tr("%1 failed to open the database.").arg(_definition.localPath()); - } - } - } else { - // Check directory again - if (!FileSystem::fileExists(_definition.localPath(), fi)) { - error = tr("Local folder %1 does not exist.").arg(_definition.localPath()); - } else if (!fi.isDir()) { - error = tr("%1 should be a folder but is not.").arg(_definition.localPath()); - } else if (!fi.isReadable()) { - error = tr("%1 is not readable.").arg(_definition.localPath()); - } else if (!fi.isWritable()) { - error = tr("%1 is not writable.").arg(_definition.localPath()); - } - } - if (!error.isEmpty()) { - qCWarning(lcFolder) << error; - _syncResult.appendErrorString(error); - setSyncState(SyncResult::SetupError); - return false; - } - return true; -} - SyncOptions Folder::loadSyncOptions() { + if (!_accountState || !_accountState->account() || !_vfs) + return SyncOptions(); + SyncOptions opt(_vfs); - ConfigFile cfgFile; + opt._parallelNetworkJobs = _accountState->account()->isHttp2Supported() ? 20 : 6; - opt._moveFilesToTrash = cfgFile.moveToTrash(); - // got a nullptr hit here - this is so shady but best I can do for now - opt._parallelNetworkJobs = (_accountState && _accountState->account() && _accountState->account()->isHttp2Supported()) ? 20 : 6; + // apparently the env can override the default + int maxParallel = qEnvironmentVariableIntValue("OWNCLOUD_MAX_PARALLEL"); + if (maxParallel > 0) + opt._parallelNetworkJobs = maxParallel; - opt.fillFromEnvironmentVariables(); return opt; } -void Folder::prepareFolder(const QString &path) -{ -#ifdef Q_OS_WIN - // First create a Desktop.ini so that the folder and favorite link show our application's icon. - const QFileInfo desktopIniPath{QStringLiteral("%1/Desktop.ini").arg(path)}; - { - const QString updateIconKey = QStringLiteral("%1/UpdateIcon").arg(Theme::instance()->appName()); - QSettings desktopIni(desktopIniPath.absoluteFilePath(), QSettings::IniFormat); - if (desktopIni.value(updateIconKey, true).toBool()) { - qCInfo(lcFolder) << "Creating" << desktopIni.fileName() << "to set a folder icon in Explorer."; - desktopIni.setValue(QStringLiteral(".ShellClassInfo/IconResource"), QDir::toNativeSeparators(qApp->applicationFilePath())); - desktopIni.setValue(updateIconKey, true); - } else { - qCInfo(lcFolder) << "Skip icon update for" << desktopIni.fileName() << "," << updateIconKey << "is disabled"; - } - } - - const QString longFolderPath = FileSystem::longWinPath(path); - const QString longDesktopIniPath = FileSystem::longWinPath(desktopIniPath.absoluteFilePath()); - // Set the folder as system and Desktop.ini as hidden+system for explorer to pick it. - // https://msdn.microsoft.com/en-us/library/windows/desktop/cc144102 - const DWORD folderAttrs = GetFileAttributesW(reinterpret_cast(longFolderPath.utf16())); - if (!SetFileAttributesW(reinterpret_cast(longFolderPath.utf16()), folderAttrs | FILE_ATTRIBUTE_SYSTEM)) { - const auto error = GetLastError(); - qCWarning(lcFolder) << "SetFileAttributesW failed on" << longFolderPath << Utility::formatWinError(error); - } - if (!SetFileAttributesW(reinterpret_cast(longDesktopIniPath.utf16()), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) { - const auto error = GetLastError(); - qCWarning(lcFolder) << "SetFileAttributesW failed on" << longDesktopIniPath << Utility::formatWinError(error); - } -#else - Q_UNUSED(path) -#endif -} - QString Folder::displayName() const { if (auto *s = space()) { @@ -323,7 +232,7 @@ QString Folder::shortGuiLocalPath() const QString Folder::cleanPath() const { - QString cleanedPath = QDir::cleanPath(_canonicalLocalPath); + QString cleanedPath = QDir::cleanPath(_definition.canonicalPath()); if (cleanedPath.length() == 3 && cleanedPath.endsWith(QLatin1String(":/"))) cleanedPath.remove(2, 1); @@ -535,28 +444,30 @@ void Folder::startVfs() return; } - VfsSetupParams vfsParams(_accountState->account(), webDavUrl(), _engine.get()); + VfsSetupParams vfsParams(_accountState->account(), webDavUrl(), _engine); vfsParams.filesystemPath = path(); vfsParams.remotePath = remotePathTrailingSlash(); - vfsParams.journal = &_journal; + vfsParams.journal = _journal; vfsParams.providerDisplayName = Theme::instance()->appNameGUI(); vfsParams.providerName = Theme::instance()->appName(); vfsParams.providerVersion = Version::version(); vfsParams.multipleAccountsRegistered = AccountManager::instance()->accounts().size() > 1; - connect(&_engine->syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged, _vfs.get(), &Vfs::fileStatusChanged); + // lord almighty - the engine should just emit this itself...these layers of indirection create so much noise and I'm betting there's no + // good reason to have an accessor on the file status tracker + connect(_engine->syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged, _vfs, &Vfs::fileStatusChanged); - connect(_vfs.get(), &Vfs::started, this, [this] { + connect(_vfs, &Vfs::started, this, [this] { // Immediately mark the sqlite temporaries as excluded. They get recreated // on db-open and need to get marked again every time. - QString stateDbFile = _journal.databaseFilePath(); + QString stateDbFile = _journal->databaseFilePath(); _vfs->fileStatusChanged(stateDbFile + QStringLiteral("-wal"), SyncFileStatus::StatusExcluded); _vfs->fileStatusChanged(stateDbFile + QStringLiteral("-shm"), SyncFileStatus::StatusExcluded); _engine->setSyncOptions(loadSyncOptions()); registerFolderWatcher(); - connect(_vfs.get(), &Vfs::needSync, this, [this] { + connect(_vfs, &Vfs::needSync, this, [this] { if (canSync()) { // the vfs plugin detected that its metadata is out of sync and requests a new sync // the request has a high priority as it is probably issued after a user request @@ -573,7 +484,7 @@ void Folder::startVfs() FolderMan::instance()->scheduler()->enqueueFolder(this); } }); - connect(_vfs.get(), &Vfs::error, this, [this](const QString &error) { + connect(_vfs, &Vfs::error, this, [this](const QString &error) { _syncResult.appendErrorString(error); setSyncState(SyncResult::SetupError); _vfsIsReady = false; @@ -588,8 +499,7 @@ void Folder::slotDiscardDownloadProgress() // Delete from journal and from filesystem. QDir folderpath(_definition.localPath()); QSet keep_nothing; - const QVector deleted_infos = - _journal.getAndDeleteStaleDownloadInfos(keep_nothing); + const QVector deleted_infos = _journal->getAndDeleteStaleDownloadInfos(keep_nothing); for (const auto &deleted_info : deleted_infos) { const QString tmppath = folderpath.filePath(deleted_info._tmpfile); qCInfo(lcFolder) << "Deleting temporary file: " << tmppath; @@ -599,7 +509,7 @@ void Folder::slotDiscardDownloadProgress() int Folder::slotWipeErrorBlacklist() { - return _journal.wipeErrorBlacklist(); + return _journal->wipeErrorBlacklist(); } void Folder::slotWatchedPathsChanged(const QSet &paths, ChangeReason reason) @@ -640,7 +550,7 @@ void Folder::slotWatchedPathsChanged(const QSet &paths, ChangeReason re _localDiscoveryTracker->addTouchedPath(relativePath); SyncJournalFileRecord record; - _journal.getFileRecord(relativePath.toUtf8(), &record); + _journal->getFileRecord(relativePath.toUtf8(), &record); if (reason != ChangeReason::UnLock) { // Check that the mtime/size actually changed or there was // an attribute change (pin state) that caused the notification @@ -676,7 +586,7 @@ void Folder::implicitlyHydrateFile(const QString &relativepath) // Set in the database that we should download the file SyncJournalFileRecord record; - _journal.getFileRecord(relativepath.toUtf8(), &record); + _journal->getFileRecord(relativepath.toUtf8(), &record); if (!record.isValid()) { qCInfo(lcFolder) << "Did not find file in db"; return; @@ -686,7 +596,7 @@ void Folder::implicitlyHydrateFile(const QString &relativepath) return; } record._type = ItemTypeVirtualFileDownload; - _journal.setFileRecord(record); + _journal->setFileRecord(record); // Change the file's pin state if it's contradictory to being hydrated // (suffix-virtual file's pin state is stored at the hydrated path) @@ -730,8 +640,14 @@ void Folder::changeVfsMode(Vfs::Mode newMode) if (newMode == _definition.virtualFilesMode()) { return; } + // if we can't create the new mode, just ditch. + Vfs *newVfs = VfsPluginManager::instance().createVfsFromPlugin(newMode, this); + if (!newVfs) { + qCWarning(lcFolder) << "Unable to change vfs mode for Folder " << _definition.localPath() << " from " << _definition.virtualFilesMode() << " to " + << newMode << ". Leaving the original mode active."; + return; + } - // This is tested in TestSyncVirtualFiles::testWipeVirtualSuffixFiles, so for changes here, have them reflected in that test. const bool wasPaused = _definition.paused(); if (!wasPaused) { setSyncPaused(true); @@ -757,22 +673,26 @@ void Folder::changeVfsMode(Vfs::Mode newMode) _vfsIsReady = false; _vfs->stop(); _vfs->unregisterFolder(); - disconnect(_vfs.get(), nullptr, this, nullptr); - disconnect(&_engine->syncFileStatusTracker(), nullptr, _vfs.get(), nullptr); + disconnect(_vfs, nullptr, this, nullptr); + disconnect(_engine->syncFileStatusTracker(), nullptr, _vfs, nullptr); // _vfs is a shared pointer... // Refactor todo: who is it shared with? It appears to be shared with the SyncOptions. SyncOptions instance is then // passed to the engine. It is not clear to me how/when the options vfs shared ptr gets updated to match this // new/reset instance but this should be high prio to work this out as wow this is dangerous. the todo is basically: eval the use of // this _vfs pointer and make it consistent and SAFE across uses - _vfs.reset(VfsPluginManager::instance().createVfsFromPlugin(newMode).release()); + // also todo: we have to cover the case that the createVfsFromPlugin returns nullptr! + Vfs *oldVfs = _vfs; + _vfs = newVfs; + oldVfs->deleteLater(); // Restart VFS. _definition.setVirtualFilesMode(newMode); + // note this is "on top of" started slot handling defined in startVfs if (newMode != Vfs::Off) { // schedule blacklisted folders for rediscovery - connect(_vfs.get(), &Vfs::started, this, [oldBlacklist, this] { + connect(_vfs, &Vfs::started, this, [oldBlacklist, this] { for (const auto &entry : oldBlacklist) { journalDb()->schedulePathForRemoteDiscovery(entry); // Refactoring todo: from what I can see, in 98% of cases the return val of setPinState is ignored @@ -796,7 +716,7 @@ bool Folder::isDeployed() const bool Folder::isFileExcludedAbsolute(const QString &fullPath) const { - if (OC_ENSURE_NOT(_engine.isNull())) { + if (OC_ENSURE(_engine)) { return _engine->isExcluded(fullPath); } return true; @@ -830,17 +750,21 @@ void Folder::wipeForRemoval() // stop reacting to changes // especially the upcoming deletion of the db // Refactoring todo: this may not be safe - using deleteLater on a real pointer is probably more reasonable. - _folderWatcher.reset(); + // it's now a child of the folder so hopefully just goes away on its own now ;) + //_folderWatcher->deleteLater(); // Delete files that have been partially downloaded. slotDiscardDownloadProgress(); // Unregister the socket API so it does not keep the .sync_journal file open FolderMan::instance()->socketApi()->slotUnregisterPath(this); - _journal.close(); // close the sync journal + _journal->close(); // close the sync journal // Remove db and temporaries - const QString stateDbFile = _engine->journal()->databaseFilePath(); + // what is this? the engine's journal IS THE SAME as the folder member journal!!!! + // const QString stateDbFile = _engine->journal()->databaseFilePath(); + const QString stateDbFile = _journal->databaseFilePath(); + QFile file(stateDbFile); if (file.exists()) { @@ -861,7 +785,8 @@ void Folder::wipeForRemoval() _vfs->stop(); _vfs->unregisterFolder(); - _vfs.reset(nullptr); // warning: folder now in an invalid state + + // don't kill vfs pointer, as it is a child of the Folder we should let it be auto-deleted by normal qobject destruction sequence. } bool Folder::reloadExcludes() @@ -931,14 +856,14 @@ void Folder::startSync() _localDiscoveryTracker->startSyncFullDiscovery(); } - QMetaObject::invokeMethod(_engine.data(), &SyncEngine::startSync, Qt::QueuedConnection); + QMetaObject::invokeMethod(_engine, &SyncEngine::startSync, Qt::QueuedConnection); Q_EMIT syncStarted(); } -void Folder::reloadSyncOptions() +void Folder::setMoveToTrash(bool trashIt) { - _engine->setSyncOptions(loadSyncOptions()); + _engine->setMoveToTrash(trashIt); } void Folder::slotSyncError(const QString &message, ErrorCategory category) @@ -1085,12 +1010,12 @@ void Folder::warnOnNewExcludedItem(const SyncJournalFileRecord &record, QStringV // Note: This assumes we're getting file watcher notifications // for folders only on creation and deletion - if we got a notification // on content change that would create spurious warnings. - QFileInfo fi(_canonicalLocalPath + path); + QFileInfo fi(_definition.canonicalPath() + path); if (!fi.exists()) return; bool ok = false; - auto blacklist = _journal.getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, ok); + auto blacklist = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, ok); if (!ok) return; if (!blacklist.contains(path + QLatin1Char('/'))) @@ -1129,17 +1054,15 @@ void Folder::slotWatcherUnreliable(const QString &message) void Folder::registerFolderWatcher() { - if (!_folderWatcher.isNull()) { + if (_folderWatcher) { return; } - _folderWatcher.reset(new FolderWatcher(this)); - connect(_folderWatcher.data(), &FolderWatcher::pathChanged, this, - [this](const QSet &paths) { slotWatchedPathsChanged(paths, Folder::ChangeReason::Other); }); - connect(_folderWatcher.data(), &FolderWatcher::lostChanges, - this, &Folder::slotNextSyncFullLocalDiscovery); - connect(_folderWatcher.data(), &FolderWatcher::becameUnreliable, - this, &Folder::slotWatcherUnreliable); + _folderWatcher = new FolderWatcher(this); + connect( + _folderWatcher, &FolderWatcher::pathChanged, this, [this](const QSet &paths) { slotWatchedPathsChanged(paths, Folder::ChangeReason::Other); }); + connect(_folderWatcher, &FolderWatcher::lostChanges, this, &Folder::slotNextSyncFullLocalDiscovery); + connect(_folderWatcher, &FolderWatcher::becameUnreliable, this, &Folder::slotWatcherUnreliable); _folderWatcher->init(path()); _folderWatcher->startNotificatonTest(path() + QLatin1String(".owncloudsync.log")); } @@ -1228,6 +1151,22 @@ void FolderDefinition::setLocalPath(const QString &path) if (!_localPath.endsWith(QLatin1Char('/'))) { _localPath.append(QLatin1Char('/')); } + + QFileInfo fi(_localPath); + _canonicalLocalPath = fi.canonicalFilePath(); + // commenting this out til I can verify it can really go away now. I checked the bug report and it was filed against QFileSystemWatcher, + // so I don't understand what it has to do with deriving the canonical path in the first place? + // #ifdef Q_OS_MAC + // Workaround QTBUG-55896 (Should be fixed in Qt 5.8) + // _canonicalLocalPath = _canonicalLocalPath.normalized(QString::NormalizationForm_C); + // #endif + if (_canonicalLocalPath.isEmpty()) { + qCWarning(lcFolder) << "Broken symlink:" << _localPath; + _canonicalLocalPath = _localPath; + } else if (!_canonicalLocalPath.endsWith(QLatin1Char('/'))) { + // the canonicalPath function strips off the trailing separator so we have to add it back, apparently + _canonicalLocalPath.append(QLatin1Char('/')); + } } void FolderDefinition::setTargetPath(const QString &path) diff --git a/src/gui/folder.h b/src/gui/folder.h index 380fadf39ac..c0e67b68331 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -45,10 +45,12 @@ class SyncRunFileLog; class FolderWatcher; class LocalDiscoveryTracker; + /** * @brief The FolderDefinition class * @ingroup gui */ + class OWNCLOUDGUI_EXPORT FolderDefinition { public: @@ -83,9 +85,12 @@ class OWNCLOUDGUI_EXPORT FolderDefinition void setVirtualFilesMode(Vfs::Mode mode) { _virtualFilesMode = mode; } /// Ensure / as separator and trailing /. + /// calling set here also ensures the canonical local path is properly created void setLocalPath(const QString &path); QString localPath() const { return _localPath; } + QString canonicalPath() const { return _canonicalLocalPath; } + /// Remove ending /, then ensure starting '/': so "/foo/bar" and "/". void setTargetPath(const QString &path); @@ -141,6 +146,8 @@ class OWNCLOUDGUI_EXPORT FolderDefinition QString _displayName; /// path on local machine (always trailing /) QString _localPath; + /// canonical local path + QString _canonicalLocalPath; /// path on remote (usually no trailing /, exception "/") QString _targetPath; bool _deployed = false; @@ -174,11 +181,10 @@ class OWNCLOUDGUI_EXPORT Folder : public QObject }; Q_ENUM(ChangeReason) - static void prepareFolder(const QString &path); /** Create a new Folder */ - Folder(const FolderDefinition &definition, AccountState *accountState, std::unique_ptr &&vfs, bool ignoreHiddenFiles, QObject *parent = nullptr); + Folder(const FolderDefinition &definition, AccountState *accountState, SyncJournalDb *journal, Vfs *vfs, bool ignoreHiddenFiles, QObject *parent); ~Folder() override; /** @@ -224,7 +230,7 @@ class OWNCLOUDGUI_EXPORT Folder : public QObject /** * canonical local folder path, always ends with / */ - QString path() const { return _canonicalLocalPath; } + QString path() const { return _definition.canonicalPath(); } /** * cleaned canonical folder path, like path() but never ends with a / @@ -300,14 +306,11 @@ class OWNCLOUDGUI_EXPORT Folder : public QObject void setSyncState(SyncResult::Status state); - void reloadSyncOptions(); + void setMoveToTrash(bool trashIt); // TODO: don't expose - SyncJournalDb *journalDb() - { - return &_journal; - } + SyncJournalDb *journalDb() { return _journal; } // TODO: don't expose SyncEngine &syncEngine() { @@ -353,8 +356,6 @@ class OWNCLOUDGUI_EXPORT Folder : public QObject uint32_t sortPriority() const { return _definition.priority(); } - static Result checkPathLength(const QString &path); - /** * * @return The corresponding space object or null @@ -462,8 +463,6 @@ private Q_SLOTS: private: void showSyncResultPopup(); - bool checkLocalPath(); - SyncOptions loadSyncOptions(); /** @@ -497,10 +496,9 @@ private Q_SLOTS: QPointer _accountState; FolderDefinition _definition; - QString _canonicalLocalPath; // As returned with QFileInfo:canonicalFilePath. Always ends with "/" + // QString _canonicalLocalPath; // As returned with QFileInfo:canonicalFilePath. Always ends with "/" SyncResult _syncResult; - QScopedPointer _engine; QElapsedTimer _timeSinceLastSyncDone; QElapsedTimer _timeSinceLastSyncStart; QElapsedTimer _timeSinceLastFullLocalDiscovery; @@ -514,7 +512,9 @@ private Q_SLOTS: /// Reset when no follow-up is requested. int _consecutiveFollowUpSyncs = 0; - mutable SyncJournalDb _journal; + // the journal and engine are created in the builder, and reparented in the folder ctr. It should NEVER be null if the Folder still exists! + SyncJournalDb *_journal = nullptr; + SyncEngine *_engine = nullptr; QScopedPointer _fileLog; @@ -544,14 +544,14 @@ private Q_SLOTS: // when it goes "out of scope" - eg when this parent folder is destructed. If we have some real reason to keep it, // I think at the very least we need to be sure to use the QScopedPointerDeleteLater custom deleter as described in the docs: // QScopedPointer customPointer(new MyCustomClass); - QScopedPointer _folderWatcher; + FolderWatcher *_folderWatcher = nullptr; /** * Keeps track of locally dirty files so we can skip local discovery sometimes. */ // Refactoring todo: as above, except we need to add the possibility to pass a parent to this class's ctr to allow auto-cleanup // again: this should NOT be complicated - QScopedPointer _localDiscoveryTracker; + LocalDiscoveryTracker *_localDiscoveryTracker = nullptr; /** * The vfs mode instance (created by plugin) to use. Never null. When vfs is not in play, vfs_off is the impl used. @@ -562,6 +562,8 @@ private Q_SLOTS: // it is also false that it is never null - it is reset in wipeForRemoval // extra fun is I have no idea what happens to the instance in the SyncOptions - is it still alive relative to the engine? // I don't see any handling of the engine or SyncOptions whatsoever in wipeForRemoval so we'll need to go spelunking. - QSharedPointer _vfs; + // final endpoint is to make this a raw pointer, pass it around and let the deps wrap it in a qpointer + // reconsider parenting before this step is taken + Vfs *_vfs = nullptr; }; } diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 687191c6a1f..da3b6462423 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -14,13 +14,15 @@ #include "folderman.h" +#include "accessmanager.h" #include "account.h" #include "accountmanager.h" #include "accountstate.h" -#include "accessmanager.h" #include "common/asserts.h" #include "configfile.h" #include "folder.h" +#include "foldermanagement/folderbuilder.h" +#include "foldermanagement/foldermanagementutils.h" #include "gui/networkinformation.h" #include "guiutility.h" #include "libsync/syncengine.h" @@ -45,13 +47,11 @@ using namespace std::chrono; using namespace std::chrono_literals; namespace { -qsizetype numberOfSyncJournals(const QString &path) -{ - return QDir(path).entryList({QStringLiteral(".sync_*.db"), QStringLiteral("._sync_*.db")}, QDir::Hidden | QDir::Files).size(); -} } namespace OCC { + + Q_LOGGING_CATEGORY(lcFolderMan, "gui.folder.manager", QtInfoMsg) void TrayOverallStatusResult::addResult(Folder *f) @@ -242,6 +242,7 @@ bool FolderMan::addFoldersFromConfigByAccount(QSettings &settings, AccountState Folder *folder = addFolder(account, folderDefinition); if (!folder) { + // todo: decide if we should actively remove the folder data from the config! I think we should but let's see continue; } @@ -306,7 +307,7 @@ void FolderMan::loadSpacesAndCreateFolders(AccountState *accountState, bool useV // prepare the root - reality check this as I think the user can change this from default? Yes they can but not for auto-loaded // folders eg on account creation, which is what is happening here. const QString localDir(accountState->account()->defaultSyncRoot()); - if (!prepareFolder(localDir)) { + if (!FolderManagementUtils::prepareFolder(localDir)) { return; } @@ -699,6 +700,15 @@ bool FolderMan::validateFolderDefinition(const FolderDefinition &folderDefinitio { if (folderDefinition.id().isEmpty() || folderDefinition.journalPath().isEmpty() || !ensureFilesystemSupported(folderDefinition)) return false; + + QString pathCheck = FolderManagementUtils::validateFolderPath(folderDefinition.localPath()); + if (!pathCheck.isEmpty()) { + // does this warrant popping an error dialog? + qCWarning(lcFolderMan) << "Folder definition path check failed with error: " << pathCheck; + return false; + } + + return true; } @@ -716,18 +726,18 @@ Folder *FolderMan::addFolder(AccountState *accountState, const FolderDefinition QUuid accountId = accountState->account()->uuid(); if (Folder *f = folder(accountId, folderDefinition.spaceId())) { - qCWarning(lcFolderMan) << "Trying to add folder" << folderDefinition.localPath() << "but it already exists in folder list"; + qCWarning(lcFolderMan) << "Trying to add new folder for " << folderDefinition.localPath() << "but it already exists in folder list"; return f; } - auto vfs = VfsPluginManager::instance().createVfsFromPlugin(folderDefinition.virtualFilesMode()); - if (!vfs) { - qCWarning(lcFolderMan) << "Could not load plugin for mode" << folderDefinition.virtualFilesMode(); + FolderBuilder builder(folderDefinition); + auto folder = builder.buildFolder(accountState, _ignoreHiddenFiles, this); + + if (!folder) { + qCWarning(lcFolderMan) << "Unable to create Folder for " << folder->path() << " with spaceId " << folderDefinition.spaceId(); return nullptr; } - auto folder = new Folder(folderDefinition, accountState, std::move(vfs), _ignoreHiddenFiles, this); - qCInfo(lcFolderMan) << "Adding folder to Folder Map " << folder << folder->path(); QString spaceId = folder->spaceId(); // always add the folder even if it had a setup error - future add special handling for incomplete folders if possible @@ -761,8 +771,8 @@ void FolderMan::connectFolder(Folder *folder) connect(folder, SIGNAL(vfsModeChanged(Folder*,Vfs::Mode)), this, SLOT(saveFolder(Folder*))); // clang-format on connect( - &folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged, _socketApi.get(), &SocketApi::broadcastStatusPushMessage); - connect(folder, &Folder::watchedFileChangedExternally, &folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::slotPathTouched); + folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged, _socketApi.get(), &SocketApi::broadcastStatusPushMessage); + connect(folder, &Folder::watchedFileChangedExternally, folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::slotPathTouched); registerFolderWithSocketApi(folder); } @@ -785,8 +795,8 @@ void FolderMan::disconnectFolder(Folder *folder) disconnect(folder, nullptr, this, nullptr); disconnect(&folder->syncEngine(), nullptr, folder, nullptr); disconnect( - &folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged, _socketApi.get(), &SocketApi::broadcastStatusPushMessage); - disconnect(folder, nullptr, &folder->syncEngine().syncFileStatusTracker(), nullptr); + folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged, _socketApi.get(), &SocketApi::broadcastStatusPushMessage); + disconnect(folder, nullptr, folder->syncEngine().syncFileStatusTracker(), nullptr); } } @@ -965,7 +975,7 @@ static QString checkPathForSyncRootMarkingRecursive(const QString &path, FolderM return FolderMan::tr("The selected path is not a folder."); } - if (numberOfSyncJournals(selectedPathInfo.filePath()) != 0) { + if (FolderManagementUtils::numberOfSyncJournals(selectedPathInfo.filePath()) != 0) { return FolderMan::tr("The folder %1 is used in a folder sync connection.").arg(QDir::toNativeSeparators(selectedPathInfo.filePath())); } @@ -1048,9 +1058,9 @@ QString FolderMan::findExistingFolderAndCheckValidity(const QString &path, NewFo QNtfsPermissionCheckGuard ntfs_perm; #endif - auto pathLengthCheck = Folder::checkPathLength(path); - if (!pathLengthCheck) { - return pathLengthCheck.error(); + auto pathLengthCheck = FolderManagementUtils::checkPathLength(path); + if (!pathLengthCheck.isEmpty()) { + return pathLengthCheck; } // If this is a new folder, recurse up to the first parent that exists, to see if we can use that to create a new folder @@ -1136,19 +1146,18 @@ bool FolderMan::isSpaceSynced(GraphApi::Space *space) const return (space && folder(space->accountId(), space->id()) != nullptr); } -// this only seems to be triggered when changing the move to trash setting? -void FolderMan::slotReloadSyncOptions() +void FolderMan::slotUpdateMoveToTrash(bool trashIt) { for (auto *f : folders()) { if (f) { - f->reloadSyncOptions(); + f->setMoveToTrash(trashIt); } } } Folder *FolderMan::addFolderFromScratch(AccountState *accountState, FolderDefinition &&folderDefinition, bool useVfs) { - if (!FolderMan::prepareFolder(folderDefinition.localPath())) { + if (!FolderManagementUtils::prepareFolder(folderDefinition.localPath())) { return nullptr; } @@ -1170,9 +1179,9 @@ Folder *FolderMan::addFolderFromScratch(AccountState *accountState, FolderDefini if (newFolder->vfs().mode() != Vfs::WindowsCfApi) { Utility::setupFavLink(folderDefinition.localPath()); } - qCDebug(lcFolderMan) << "Local sync folder" << folderDefinition.localPath() << "successfully created!"; + qCDebug(lcFolderMan) << "Local sync folder" << folderDefinition.localPath() << "successfully created"; } else { - qCWarning(lcFolderMan) << "Failed to create local sync folder!"; + qCWarning(lcFolderMan) << "Failed to create local sync folder: " << folderDefinition.localPath(); } // we should not emit any folder list change from this function because it can be called in bulk as well as individual operations @@ -1218,19 +1227,7 @@ QString FolderMan::suggestSyncFolder(NewFolderType folderType, const QUuid &acco return FolderMan::instance()->findGoodPathForNewSyncFolder(QDir::homePath(), Theme::instance()->appName(), folderType, accountUuid); } -bool FolderMan::prepareFolder(const QString &folder) -{ - if (!QFileInfo::exists(folder)) { - if (!OC_ENSURE(QDir().mkpath(folder))) { - return false; - } - // mac only - FileSystem::setFolderMinimumPermissions(folder); - // this is for windows - it sets up a desktop.ini file to handle the icon and deals with persmissions. - Folder::prepareFolder(folder); - } - return true; -} + std::unique_ptr FolderMan::createInstance() { diff --git a/src/gui/folderman.h b/src/gui/folderman.h index d9d266fc135..250b0b85980 100644 --- a/src/gui/folderman.h +++ b/src/gui/folderman.h @@ -52,6 +52,7 @@ class TrayOverallStatusResult SyncResult _overallStatus; }; + /** * @brief The FolderMan class * @ingroup gui @@ -168,13 +169,16 @@ class OWNCLOUDGUI_EXPORT FolderMan : public QObject std::optional setupFoldersFromConfig(); /** + * + * Extremely important refactoring todo: addFolder should not be public! + * It is currently "required" for some tests which is not really cool, as it does not represent a complete/standalone impl. + * * core step in any add folder routine. it validates the definition, instantiates vfs, instantiates the folder and validates whether * it had setup errors. * - * it is up to the caller to connect the folder, save it to settings, etc. + * it is up to the caller to create the local sync folder (corresponding to the local path in the def) using the new FolderManagementUtils::prepareFolder, + * connect the folder, save it to settings, etc. * - * Refactoring todo: this should not be public! it is currently "required" for some tests which is not really cool, as it does not represent - * a complete/standalone impl. */ Folder *addFolder(AccountState *accountState, const FolderDefinition &folderDefinition); @@ -329,7 +333,7 @@ public Q_SLOTS: void slotSyncOnceFileUnlocks(const QString &path, FileSystem::LockMode mode); /// This slot will tell all sync engines to reload the sync options. - void slotReloadSyncOptions(); + void slotUpdateMoveToTrash(bool trashIt); // emits folderRemoved void removeFolderFromGui(Folder *f); @@ -356,13 +360,6 @@ private Q_SLOTS: private: explicit FolderMan(); - /** - * @brief prepareFolder sets up the folder with mac and windows specific operations - * @param folder path - * @return true if the folder path exists or can be successfully created - */ - [[nodiscard]] static bool prepareFolder(const QString &folder); - /** * Adds a folder "from scratch" as oppossd to from config, which requires less setup than when you create the folder * from some dynamic operation (eg folders from new account or via the gui add folder sync operations). diff --git a/src/gui/foldermanagement/CMakeLists.txt b/src/gui/foldermanagement/CMakeLists.txt new file mode 100644 index 00000000000..44e24991104 --- /dev/null +++ b/src/gui/foldermanagement/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(owncloudGui PRIVATE + foldermanagementutils.h + foldermanagementutils.cpp + folderbuilder.h + folderbuilder.cpp +) + diff --git a/src/gui/foldermanagement/folderbuilder.cpp b/src/gui/foldermanagement/folderbuilder.cpp new file mode 100644 index 00000000000..8695d16e60f --- /dev/null +++ b/src/gui/foldermanagement/folderbuilder.cpp @@ -0,0 +1,62 @@ +#include "folderbuilder.h" + +#include "common/syncjournaldb.h" +#include "common/vfs.h" +#include "folder.h" +#include "syncengine.h" + + +namespace OCC { + +Q_LOGGING_CATEGORY(lcFolderBuilder, "gui.folderbuilder", QtInfoMsg) + +FolderBuilder::FolderBuilder(const FolderDefinition &definition, QObject *parent) + : QObject(parent) + , _definition(definition) +{ +} + +Folder *FolderBuilder::buildFolder(AccountState *accountState, bool ignoreHiddenFiles, QObject *parent) +{ + if (!accountState || !accountState->account()) + return nullptr; + + SyncJournalDb *db = buildJournal(); + Vfs *vfs = buildVfs(); + if (db && vfs) + return new Folder(_definition, accountState, db, vfs, ignoreHiddenFiles, parent); + + return nullptr; +} + +SyncJournalDb *FolderBuilder::buildJournal() +{ + SyncJournalDb *journal = new SyncJournalDb(_definition.absoluteJournalPath(), this); + if (!journal->open()) { + qCWarning(lcFolderBuilder) << "Could not open database when creating new folder: " << _definition.absoluteJournalPath(); + return nullptr; + } + journal->close(); + journal->allowReopen(); + // those errors should not persist over sessions so kill them if there are any in an existing journal + journal->wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::Category::LocalSoftError); + return journal; +} + +Vfs *FolderBuilder::buildVfs() +{ + Vfs *vfs = VfsPluginManager::instance().createVfsFromPlugin(_definition.virtualFilesMode(), this); + if (vfs) + return vfs; + + qCWarning(lcFolderBuilder) << "Could not load plugin for mode" << _definition.virtualFilesMode(); + return nullptr; +} + +SyncEngine *FolderBuilder::buildEngine() +{ + return nullptr; +} + + +} diff --git a/src/gui/foldermanagement/folderbuilder.h b/src/gui/foldermanagement/folderbuilder.h new file mode 100644 index 00000000000..43ea2e4a76d --- /dev/null +++ b/src/gui/foldermanagement/folderbuilder.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "folder.h" + +namespace OCC { + +class SyncJournalDb; +class SyncEngine; +class Vfs; + +class FolderBuilder : public QObject +{ + Q_OBJECT + +public: + FolderBuilder(const FolderDefinition &definition, QObject *parent = nullptr); + + Folder *buildFolder(AccountState *accountState, bool ignoreHiddenFiles, QObject *parent); + + +private: + SyncJournalDb *buildJournal(); + SyncEngine *buildEngine(); + Vfs *buildVfs(); + + FolderDefinition _definition; +}; +} diff --git a/src/gui/foldermanagement/foldermanagementutils.cpp b/src/gui/foldermanagement/foldermanagementutils.cpp new file mode 100644 index 00000000000..18f716c39db --- /dev/null +++ b/src/gui/foldermanagement/foldermanagementutils.cpp @@ -0,0 +1,129 @@ +#include "foldermanagementutils.h" + +#include "common/asserts.h" +#include "common/filesystembase.h" +#include "theme.h" + +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include "common/utility_win.h" +#endif + + +namespace OCC { + +Q_LOGGING_CATEGORY(lcFolderManagementUtils, "gui.foldermanagementutils", QtInfoMsg) + +bool FolderManagementUtils::prepareFolder(const QString &path) +{ + if (!QFileInfo::exists(path)) { + if (!OC_ENSURE(QDir().mkpath(path))) { + return false; + } +#ifdef Q_OS_WIN + // First create a Desktop.ini so that the folder and favorite link show our application's icon. + const QFileInfo desktopIniPath{QStringLiteral("%1/Desktop.ini").arg(path)}; + { + const QString updateIconKey = QStringLiteral("%1/UpdateIcon").arg(Theme::instance()->appName()); + QSettings desktopIni(desktopIniPath.absoluteFilePath(), QSettings::IniFormat); + if (desktopIni.value(updateIconKey, true).toBool()) { + qCInfo(lcFolderManagementUtils) << "Creating" << desktopIni.fileName() << "to set a folder icon in Explorer."; + desktopIni.setValue(QStringLiteral(".ShellClassInfo/IconResource"), QDir::toNativeSeparators(qApp->applicationFilePath())); + desktopIni.setValue(updateIconKey, true); + } else { + qCInfo(lcFolderManagementUtils) << "Skip icon update for" << desktopIni.fileName() << "," << updateIconKey << "is disabled"; + } + } + + const QString longFolderPath = FileSystem::longWinPath(path); + const QString longDesktopIniPath = FileSystem::longWinPath(desktopIniPath.absoluteFilePath()); + // Set the folder as system and Desktop.ini as hidden+system for explorer to pick it. + // https://msdn.microsoft.com/en-us/library/windows/desktop/cc144102 + const DWORD folderAttrs = GetFileAttributesW(reinterpret_cast(longFolderPath.utf16())); + if (!SetFileAttributesW(reinterpret_cast(longFolderPath.utf16()), folderAttrs | FILE_ATTRIBUTE_SYSTEM)) { + const auto error = GetLastError(); + qCWarning(lcFolderManagementUtils) << "SetFileAttributesW failed on" << longFolderPath << Utility::formatWinError(error); + return false; + } + if (!SetFileAttributesW(reinterpret_cast(longDesktopIniPath.utf16()), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) { + const auto error = GetLastError(); + qCWarning(lcFolderManagementUtils) << "SetFileAttributesW failed on" << longDesktopIniPath << Utility::formatWinError(error); + return false; + } +#else + QFile::Permissions perm = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; + QFile file(path); + if (!file.setPermissions(perm)) { + qCWarning(lcFolderManagementUtils) << "setPermissions failed on " << path; + return false; + } + +#endif + } + return true; +} + +qsizetype FolderManagementUtils::numberOfSyncJournals(const QString &path) +{ + return QDir(path).entryList({QStringLiteral(".sync_*.db"), QStringLiteral("._sync_*.db")}, QDir::Hidden | QDir::Files).size(); +} + +QString FolderManagementUtils::checkPathLength(const QString &path) +{ +#ifdef Q_OS_WIN + if (path.size() > MAX_PATH) { + if (!FileSystem::longPathsEnabledOnWindows()) { + return tr("The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder.").arg(path); + } + } +#else + Q_UNUSED(path) +#endif + return {}; +} + +QString FolderManagementUtils::validateFolderPath(const QString &path) +{ +#ifdef Q_OS_WIN + QNtfsPermissionCheckGuard ntfs_perm; +#endif + const QFileInfo fi(path); + QString error; + if (fi.isDir() && fi.isReadable() && fi.isWritable()) { + auto pathLengthCheck = FolderManagementUtils::checkPathLength(fi.canonicalFilePath()); + if (!pathLengthCheck.isEmpty()) { + error = pathLengthCheck; + } + + /* if (error.isEmpty()) { + qCDebug(lcFolderManagementUtils) << "Checked local path ok"; + if (!_journal.open()) { + error = tr("%1 failed to open the database.").arg(_definition.localPath()); + } + }*/ + } else { + // Check directory again + if (!FileSystem::fileExists(path, fi)) { + error = tr("Local folder %1 does not exist.").arg(path); + } else if (!fi.isDir()) { + error = tr("%1 should be a folder but is not.").arg(path); + } else if (!fi.isReadable()) { + error = tr("%1 is not readable.").arg(path); + } else if (!fi.isWritable()) { + error = tr("%1 is not writable.").arg(path); + } + } + if (!error.isEmpty()) { + qCWarning(lcFolderManagementUtils) << error; + // _syncResult.appendErrorString(error); + // setSyncState(SyncResult::SetupError); + // return error; + } + return error; +} + +} // OCC diff --git a/src/gui/foldermanagement/foldermanagementutils.h b/src/gui/foldermanagement/foldermanagementutils.h new file mode 100644 index 00000000000..cc94d69a5fc --- /dev/null +++ b/src/gui/foldermanagement/foldermanagementutils.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +namespace OCC { +/** + * @brief The FolderManagementUtils class is a "controlled" collection of general functions related to managing folders and their paths. + * + * indeed, this could be a namespace but I have seen too many abuses of "a namespace can be defined ANYWHERE" to be comfortable with it + * + * Declaring a final, non instantiable class prevents any creative "namespace" decls/defs + * + */ +class FolderManagementUtils final +{ + Q_DECLARE_TR_FUNCTIONS(FolderManagementUtils) + +public: + FolderManagementUtils() = delete; + ~FolderManagementUtils() = delete; + + /** + * @brief prepareFolder creates the folder from given path if it does not already exist, and configures the folder with mac and windows + * specific operations + * @param folder path + * @return true if the folder path exists and was successfully configured + */ + static bool prepareFolder(const QString &path); + + /** + * @brief numberOfSyncJournals counts sync journals by matching folder content with .sync_*.db or ._sync_*.db in the target folder + * @param path is the folder to check for number of sync journals + * @return the number of sync journals found in the given folder. + */ + static qsizetype numberOfSyncJournals(const QString &path); + + /** + * verifies that the length of the given path does not exceed system limits + * if the path fails the check, the return value will contain an error string + * if it passes, the return value will be empty + **/ + static QString checkPathLength(const QString &path); + + /** + * performs various checks on the folder path to ensure it exists and can be used as local sync destination. + * if the checks fail, the return value will contain the error + * if it passes, the return value will be empty. + **/ + static QString validateFolderPath(const QString &path); +}; +} diff --git a/src/gui/folderwatcher.h b/src/gui/folderwatcher.h index 19501ecbae1..cbb76dbb649 100644 --- a/src/gui/folderwatcher.h +++ b/src/gui/folderwatcher.h @@ -51,7 +51,7 @@ class OWNCLOUDGUI_EXPORT FolderWatcher : public QObject Q_OBJECT public: // Construct, connect signals, call init() - explicit FolderWatcher(Folder *folder = nullptr); + explicit FolderWatcher(Folder *folder); ~FolderWatcher() override; /** diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp index 9b490b4aa3c..d43c56605dc 100644 --- a/src/gui/generalsettings.cpp +++ b/src/gui/generalsettings.cpp @@ -66,7 +66,7 @@ GeneralSettings::GeneralSettings(QWidget *parent) _ui->moveToTrashCheckBox->setVisible(true); connect(_ui->moveToTrashCheckBox, &QCheckBox::toggled, this, [this](bool checked) { ConfigFile().setMoveToTrash(checked); - Q_EMIT syncOptionsChanged(); + Q_EMIT moveToTrashChanged(checked); }); // OEM themes are not obliged to ship mono icons, so there diff --git a/src/gui/generalsettings.h b/src/gui/generalsettings.h index e5c97593091..d3bec7ff33c 100644 --- a/src/gui/generalsettings.h +++ b/src/gui/generalsettings.h @@ -41,7 +41,7 @@ class GeneralSettings : public QWidget Q_SIGNALS: void showAbout(); - void syncOptionsChanged(); + void moveToTrashChanged(bool trashIt); private Q_SLOTS: void saveMiscSettings(); diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp index 645e09ed3c5..7cd3e0bf0af 100644 --- a/src/gui/settingsdialog.cpp +++ b/src/gui/settingsdialog.cpp @@ -121,7 +121,7 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) _generalSettings = new GeneralSettings; _ui->stack->addWidget(_generalSettings); connect(_generalSettings, &GeneralSettings::showAbout, gui, &ownCloudGui::slotAbout); - connect(_generalSettings, &GeneralSettings::syncOptionsChanged, FolderMan::instance(), &FolderMan::slotReloadSyncOptions); + connect(_generalSettings, &GeneralSettings::moveToTrashChanged, FolderMan::instance(), &FolderMan::slotUpdateMoveToTrash); ConfigFile().restoreGeometry(this); #ifdef Q_OS_MAC diff --git a/src/gui/socketapi/socketapi.cpp b/src/gui/socketapi/socketapi.cpp index ba73cf2e7fe..a89d8e23377 100644 --- a/src/gui/socketapi/socketapi.cpp +++ b/src/gui/socketapi/socketapi.cpp @@ -353,7 +353,7 @@ void SocketApi::slotUpdateFolderView(Folder *f) Q_FALLTHROUGH(); case SyncResult::Error: { const QString rootPath = Utility::stripTrailingSlash(f->path()); - broadcastStatusPushMessage(rootPath, f->syncEngine().syncFileStatusTracker().fileStatus(QString())); + broadcastStatusPushMessage(rootPath, f->syncEngine().syncFileStatusTracker()->fileStatus(QString())); broadcastMessage(buildMessage(QStringLiteral("UPDATE_VIEW"), rootPath)); break; @@ -768,7 +768,7 @@ SyncFileStatus SocketApi::FileData::syncFileStatus() const if (!folder || !folder->canSync()) { return SyncFileStatus::StatusNone; } - return folder->syncEngine().syncFileStatusTracker().fileStatus(folderRelativePath); + return folder->syncEngine().syncFileStatusTracker()->fileStatus(folderRelativePath); } SyncJournalFileRecord SocketApi::FileData::journalRecord() const diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 9a59d543a20..b1e753dae13 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -343,6 +343,11 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( const SyncFileItemPtr &item, PathTuple path, const LocalInfo &localEntry, const RemoteInfo &serverEntry, const SyncJournalFileRecord &dbEntry) { + if (!_discoveryData->_syncOptions.isValid()) { + qCWarning(lcDisco) << "vfs instance is null, unable to continue"; + return; + } + item->_checksumHeader = serverEntry.checksumHeader; item->_fileId = serverEntry.fileId; item->_remotePerm = serverEntry.remotePerm; @@ -387,7 +392,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( item->_size = serverEntry.size; if (dbEntry.isDirectory()) { // TODO: move the decision to the backend - if (_discoveryData->_syncOptions._vfs->mode() != Vfs::Off && _pinState != PinState::AlwaysLocal) { + if (_discoveryData->_syncOptions.vfs()->mode() != Vfs::Off && _pinState != PinState::AlwaysLocal) { item->_type = ItemTypeVirtualFile; } } @@ -435,10 +440,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( auto postProcessServerNew = [=]() mutable { // Turn new remote files into virtual files if the option is enabled. // TODO: move the decision to the backend - const auto &opts = _discoveryData->_syncOptions; - if (!localEntry.isValid() - && item->_type == ItemTypeFile - && opts._vfs->mode() != Vfs::Off + if (!localEntry.isValid() && item->_type == ItemTypeFile && _discoveryData->_syncOptions.vfs()->mode() != Vfs::Off && _pinState != PinState::AlwaysLocal) { item->_type = ItemTypeVirtualFile; } @@ -663,6 +665,10 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( const SyncFileItemPtr &item, const PathTuple &path, const LocalInfo &localEntry, const RemoteInfo &serverEntry, const SyncJournalFileRecord &dbEntry, QueryMode recurseQueryServer) { + if (!_discoveryData->_syncOptions.isValid()) { + qCWarning(lcDisco) << "vfs instance is null, unable to continue."; + return; + } const bool noServerEntry = (_queryServer != ParentNotChanged && !serverEntry.isValid()) || (_queryServer == ParentNotChanged && !dbEntry.isValid()); if (noServerEntry) { @@ -754,9 +760,7 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( item->_direction = SyncFileItem::Down; item->setInstruction(CSYNC_INSTRUCTION_SYNC); item->_type = ItemTypeVirtualFileDehydration; - } else if (!serverModified - && (dbEntry._inode != localEntry.inode - || _discoveryData->_syncOptions._vfs->needsMetadataUpdate(*item))) { + } else if (!serverModified && (dbEntry._inode != localEntry.inode || _discoveryData->_syncOptions.vfs()->needsMetadataUpdate(*item))) { item->setInstruction(CSYNC_INSTRUCTION_UPDATE_METADATA); item->_direction = SyncFileItem::Down; } @@ -829,7 +833,7 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( auto postProcessLocalNew = [item, localEntry, this](const PathTuple &path) { if (localEntry.isVirtualFile) { - const bool isPlaceHolder = _discoveryData->_syncOptions._vfs->isDehydratedPlaceholder(_discoveryData->_localDir + path._local); + const bool isPlaceHolder = _discoveryData->_syncOptions.vfs()->isDehydratedPlaceholder(_discoveryData->_localDir + path._local); if (isPlaceHolder) { qCWarning(lcDisco) << "Wiping virtual file without db entry for" << path._local; item->setInstruction(CSYNC_INSTRUCTION_REMOVE); @@ -1371,8 +1375,13 @@ DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery() void ProcessDirectoryJob::startAsyncLocalQuery() { + if (!_discoveryData->_syncOptions.isValid()) { + qCInfo(lcDisco) << "vfs pointer is null, unable to continue"; + return; + } + QString localPath = _discoveryData->_localDir + _currentFolder._local; - auto localJob = new DiscoverySingleLocalDirectoryJob(localPath, _discoveryData->_syncOptions._vfs.data()); + auto localJob = new DiscoverySingleLocalDirectoryJob(localPath, _discoveryData->_syncOptions.vfs()); _discoveryData->_currentlyActiveJobs++; _pendingAsyncJobs++; @@ -1423,9 +1432,14 @@ void ProcessDirectoryJob::startAsyncLocalQuery() void ProcessDirectoryJob::computePinState(PinState parentState) { + if (!_discoveryData->_syncOptions.isValid()) { + qCInfo(lcDisco) << "vfs pointer is null, unable to continue"; + return; + } + _pinState = parentState; if (_queryLocal != ParentDontExist) { - if (auto state = _discoveryData->_syncOptions._vfs->pinState(_currentFolder._local)) // ouch! pin local or original? + if (auto state = _discoveryData->_syncOptions.vfs()->pinState(_currentFolder._local)) // ouch! pin local or original? _pinState = *state; } } diff --git a/src/libsync/discovery.h b/src/libsync/discovery.h index 9284ca2f4aa..90570b1615d 100644 --- a/src/libsync/discovery.h +++ b/src/libsync/discovery.h @@ -253,7 +253,7 @@ class ProcessDirectoryJob : public QObject std::deque _queuedJobs; QVector _runningJobs; - DiscoveryPhase *_discoveryData; + QPointer _discoveryData; PathTuple _currentFolder; bool _childModified = false; // the directory contains modified item what would prevent deletion diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index fad08cee980..cb270bc5c0f 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -91,7 +91,7 @@ class DiscoverySingleLocalDirectoryJob : public QObject, public QRunnable void itemDiscovered(SyncFileItemPtr item); void childIgnored(bool b); -private Q_SLOTS: + private: QString _localPath; OCC::Vfs* _vfs; @@ -228,7 +228,7 @@ class DiscoveryPhase : public QObject public: // input - DiscoveryPhase(Account *account, const SyncOptions &options, const QUrl &baseUrl, QObject *parent = nullptr) + DiscoveryPhase(Account *account, const SyncOptions &options, const QUrl &baseUrl, QObject *parent) : QObject(parent) , _syncOptions(options) , _baseUrl(baseUrl) diff --git a/src/libsync/localdiscoverytracker.cpp b/src/libsync/localdiscoverytracker.cpp index 387d1599b09..37b8b4bfc91 100644 --- a/src/libsync/localdiscoverytracker.cpp +++ b/src/libsync/localdiscoverytracker.cpp @@ -22,7 +22,8 @@ using namespace OCC; Q_LOGGING_CATEGORY(lcLocalDiscoveryTracker, "sync.localdiscoverytracker", QtInfoMsg) -LocalDiscoveryTracker::LocalDiscoveryTracker() +LocalDiscoveryTracker::LocalDiscoveryTracker(QObject *parent) + : QObject(parent) { } diff --git a/src/libsync/localdiscoverytracker.h b/src/libsync/localdiscoverytracker.h index ab27733480f..ade70ecf634 100644 --- a/src/libsync/localdiscoverytracker.h +++ b/src/libsync/localdiscoverytracker.h @@ -50,7 +50,7 @@ class OWNCLOUDSYNC_EXPORT LocalDiscoveryTracker : public QObject { Q_OBJECT public: - LocalDiscoveryTracker(); + LocalDiscoveryTracker(QObject *parent); /** Adds a path that must be locally rediscovered later. * diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index 3022357f982..e2384b81c69 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -805,18 +805,23 @@ QString OwncloudPropagator::adjustRenamedPath(const QString &original) const Result OwncloudPropagator::updatePlaceholder(const SyncFileItem &item, const QString &fileName, const QString &replacesFile) { + if (!_syncOptions.isValid()) { + QString error = "vfs instance is not available"; + return error; + } + Q_ASSERT([&] { if (item._type == ItemTypeVirtualFileDehydration) { // when dehydrating the file must not be pinned // don't use destination() with suffix placeholder - const auto pin = syncOptions()._vfs->pinState(item._file); + const auto pin = syncOptions().vfs()->pinState(item._file); if (pin && pin.get() == PinState::AlwaysLocal) { return false; } } return true; }()); - return syncOptions()._vfs->updateMetadata(item, fileName, replacesFile); + return syncOptions().vfs()->updateMetadata(item, fileName, replacesFile); } Result OwncloudPropagator::updateMetadata(const SyncFileItem &item) @@ -842,6 +847,7 @@ Result OwncloudPropagator::updateMetad PropagatorJob::PropagatorJob(OwncloudPropagator *propagator, const QString &path) : QObject(propagator) + , _propagator(propagator) , _path(path) , _jobState(NotYetStarted) { @@ -854,7 +860,10 @@ void PropagatorJob::setState(JobState state) OwncloudPropagator *PropagatorJob::propagator() const { - return qobject_cast(parent()); + // this is so shady, but so far Linux tests seem to segfault all over the place if the member is returned. + // so shady. nope, it didn't fix anything that I could see + // return qobject_cast(parent()); + return _propagator; } // ================================================================================ diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h index 827a1cd9e21..0df714a837a 100644 --- a/src/libsync/owncloudpropagator.h +++ b/src/libsync/owncloudpropagator.h @@ -135,6 +135,7 @@ public Q_SLOTS: OwncloudPropagator *propagator() const; private: + OwncloudPropagator *_propagator; QString _path; JobState _jobState; }; @@ -366,9 +367,10 @@ class OWNCLOUDSYNC_EXPORT OwncloudPropagator : public QObject bool _finishedEmitted; // used to ensure that finished is only emitted once public: - OwncloudPropagator( - Account *account, const SyncOptions &options, const QUrl &baseUrl, const QString &localDir, const QString &remoteFolder, SyncJournalDb *progressDb) - : _journal(progressDb) + OwncloudPropagator(Account *account, const SyncOptions &options, const QUrl &baseUrl, const QString &localDir, const QString &remoteFolder, + SyncJournalDb *progressDb, QObject *parent) + : QObject(parent) + , _journal(progressDb) , _finishedEmitted(false) , _anotherSyncNeeded(false) , _account(account) @@ -474,6 +476,9 @@ class OWNCLOUDSYNC_EXPORT OwncloudPropagator : public QObject */ DiskSpaceResult diskSpaceCheck() const; + bool moveToTrash() const { return _moveToTrash; } + void setMoveToTrash(bool trashIt) { _moveToTrash = trashIt; } + /** Handles a conflict by renaming the file 'item'. * * Sets up conflict records. @@ -539,10 +544,11 @@ private Q_SLOTS: QScopedPointer _rootJob; SyncOptions _syncOptions; bool _jobScheduled = false; + bool _moveToTrash = false; const QString _localDir; // absolute path to the local directory. ends with '/' const QString _remoteFolder; // remote folder, ends with '/' - const QUrl _webDavUrl; // full WebDAV URL, might be the same as in the account + const QUrl _webDavUrl; // full WebDAV URL, might be the same as in the }; /** diff --git a/src/libsync/owncloudpropagator_p.h b/src/libsync/owncloudpropagator_p.h index da2b0282b16..69c410335a4 100644 --- a/src/libsync/owncloudpropagator_p.h +++ b/src/libsync/owncloudpropagator_p.h @@ -15,9 +15,7 @@ #pragma once -#include "owncloudpropagator.h" #include "syncfileitem.h" -#include "networkjobs.h" #include #include diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 6fc6a68d922..69bdcf4ac33 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -354,13 +354,12 @@ QString GETFileJob::errorString() const void PropagateDownloadFile::start() { - if (propagator()->_abortRequested) + if (propagator()->_abortRequested || !propagator()->syncOptions().isValid()) return; _stopwatch.start(); - auto &syncOptions = propagator()->syncOptions(); - auto &vfs = syncOptions._vfs; + Vfs *vfs = propagator()->syncOptions().vfs(); const QString fsPath = propagator()->fullLocalPath(_item->_file); diff --git a/src/libsync/propagateremotedelete.cpp b/src/libsync/propagateremotedelete.cpp index 6e68f20d686..ae927a23752 100644 --- a/src/libsync/propagateremotedelete.cpp +++ b/src/libsync/propagateremotedelete.cpp @@ -13,9 +13,9 @@ */ #include "propagateremotedelete.h" -#include "owncloudpropagator_p.h" #include "account.h" #include "common/asserts.h" +#include "owncloudpropagator_p.h" #include diff --git a/src/libsync/propagateremotemkdir.cpp b/src/libsync/propagateremotemkdir.cpp index 1cc2fcba843..33561ec4731 100644 --- a/src/libsync/propagateremotemkdir.cpp +++ b/src/libsync/propagateremotemkdir.cpp @@ -13,11 +13,11 @@ */ #include "propagateremotemkdir.h" -#include "owncloudpropagator_p.h" #include "account.h" +#include "common/asserts.h" #include "common/syncjournalfilerecord.h" +#include "owncloudpropagator_p.h" #include "propagateremotedelete.h" -#include "common/asserts.h" #include #include diff --git a/src/libsync/propagateremotemove.cpp b/src/libsync/propagateremotemove.cpp index 3695fbc10ec..8e72cf63d6e 100644 --- a/src/libsync/propagateremotemove.cpp +++ b/src/libsync/propagateremotemove.cpp @@ -121,6 +121,10 @@ void PropagateRemoteMove::slotMoveJobFinished() void PropagateRemoteMove::finalize() { + if (!propagator()->syncOptions().isValid()) { + qCWarning(lcPropagateRemoteMove) << "vfs instance is null, unable to finalize"; + return; + } // Retrieve old db data. // if reading from db failed still continue hoping that deleteFileRecord // reopens the db successfully. @@ -128,7 +132,7 @@ void PropagateRemoteMove::finalize() // to the new record. It is not a problem to skip it here. SyncJournalFileRecord oldRecord; propagator()->_journal->getFileRecord(_item->_originalFile, &oldRecord); - auto &vfs = propagator()->syncOptions()._vfs; + Vfs *vfs = propagator()->syncOptions().vfs(); auto pinState = vfs->pinState(_item->_originalFile); // Delete old db data. diff --git a/src/libsync/propagateuploadcommon.cpp b/src/libsync/propagateuploadcommon.cpp index 2c8c19d3a3c..3f4751e3265 100644 --- a/src/libsync/propagateuploadcommon.cpp +++ b/src/libsync/propagateuploadcommon.cpp @@ -12,19 +12,19 @@ * for more details. */ -#include "account.h" -#include "filesystem.h" -#include "networkjobs.h" -#include "owncloudpropagator_p.h" -#include "propagateremotedelete.h" -#include "propagateuploadfile.h" -#include "syncengine.h" +#include "propagateuploadcommon.h" +#include "account.h" #include "common/asserts.h" #include "common/checksums.h" #include "common/syncjournaldb.h" #include "common/syncjournalfilerecord.h" #include "common/utility.h" +#include "filesystem.h" +#include "networkjobs.h" +#include "owncloudpropagator_p.h" +#include "propagateremotedelete.h" +#include "syncengine.h" #include "libsync/theme.h" @@ -32,7 +32,7 @@ #include #include -#include +// #include using namespace std::chrono_literals; @@ -261,8 +261,7 @@ void PropagateUploadCommon::commonErrorHandling(AbstractNetworkJob *job) // Ensure errors that should eventually reset the chunked upload are tracked. checkResettingErrors(); - SyncFileItem::Status status = classifyError(job->reply()->error(), _item->_httpErrorCode, - &propagator()->_anotherSyncNeeded, replyContent); + SyncFileItem::Status status = classifyError(job->reply()->error(), _item->_httpErrorCode, &propagator()->_anotherSyncNeeded, replyContent); // Insufficient remote storage. if (_item->_httpErrorCode == 507) { @@ -359,7 +358,7 @@ QMap PropagateUploadCommon::headers() void PropagateUploadCommon::finalize() { - OC_ENFORCE(state() != Finished); + OC_ENFORCE(state() != Finished && propagator()->syncOptions().isValid()); // Update the quota, if known if (!_quotaUpdated) { @@ -397,7 +396,7 @@ void PropagateUploadCommon::finalize() // Files that were new on the remote shouldn't have online-only pin state // even if their parent folder is online-only. if (_item->instruction() & (CSYNC_INSTRUCTION_NEW | CSYNC_INSTRUCTION_TYPE_CHANGE)) { - auto &vfs = propagator()->syncOptions()._vfs; + Vfs *vfs = propagator()->syncOptions().vfs(); const auto pin = vfs->pinState(_item->_file); if (pin && *pin == PinState::OnlineOnly) { std::ignore = vfs->setPinState(_item->_file, PinState::Unspecified); diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp index cbdbc9b3987..0a6224d93c0 100644 --- a/src/libsync/propagatorjobs.cpp +++ b/src/libsync/propagatorjobs.cpp @@ -17,8 +17,6 @@ #include "common/syncjournaldb.h" #include "common/syncjournalfilerecord.h" #include "filesystem.h" -#include "owncloudpropagator.h" -#include "owncloudpropagator_p.h" #include "propagateremotemove.h" #include #include @@ -95,7 +93,7 @@ bool PropagateLocalRemove::removeRecursively(const QString &absolute) void PropagateLocalRemove::start() { - _moveToTrash = propagator()->syncOptions()._moveFilesToTrash; + _moveToTrash = propagator()->moveToTrash(); if (propagator()->_abortRequested) return; @@ -212,7 +210,7 @@ void PropagateLocalMkdir::setDeleteExistingFile(bool enabled) void PropagateLocalRename::start() { - if (propagator()->_abortRequested) + if (propagator()->_abortRequested || !propagator()->syncOptions().isValid()) return; QString existingFile = propagator()->fullLocalPath(propagator()->adjustRenamedPath(_item->_file)); @@ -253,7 +251,7 @@ void PropagateLocalRename::start() propagator()->_journal->getFileRecord(_item->_originalFile, &oldRecord); propagator()->_journal->deleteFileRecord(_item->_originalFile); - auto &vfs = propagator()->syncOptions()._vfs; + Vfs *vfs = propagator()->syncOptions().vfs(); auto pinState = vfs->pinState(_item->_originalFile); std::ignore = vfs->setPinState(_item->_originalFile, PinState::Inherited); diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 320854c8f11..fe32d7d157c 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -43,8 +43,9 @@ Q_LOGGING_CATEGORY(lcEngine, "sync.engine", QtInfoMsg) // doc in header std::chrono::seconds SyncEngine::minimumFileAgeForUpload(2s); -SyncEngine::SyncEngine(Account *account, const QUrl &baseUrl, const QString &localPath, const QString &remotePath, OCC::SyncJournalDb *journal) - : _account(account) +SyncEngine::SyncEngine(Account *account, const QUrl &baseUrl, const QString &localPath, const QString &remotePath, OCC::SyncJournalDb *journal, QObject *parent) + : QObject(parent) + , _account(account) , _baseUrl(baseUrl) , _needsUpdate(false) , _syncRunning(false) @@ -58,26 +59,20 @@ SyncEngine::SyncEngine(Account *account, const QUrl &baseUrl, const QString &loc // be passed between threads and b) to just call it once. // suggest calling registration method in FolderMan or one of the other single instance managers on startup // one day it might belong in an app builder routine. - qRegisterMetaType("SyncFileItem"); - qRegisterMetaType("SyncFileItemPtr"); - qRegisterMetaType("SyncFileItem::Status"); - qRegisterMetaType("SyncFileStatus"); - qRegisterMetaType("SyncFileItemSet"); - qRegisterMetaType("SyncFileItem::Direction"); + // Everything in the SyncEngine expects a trailing slash for the localPath. OC_ASSERT(localPath.endsWith(QLatin1Char('/'))); - _excludedFiles.reset(new ExcludedFiles); + _excludedFiles = new ExcludedFiles(this); - _syncFileStatusTracker.reset(new SyncFileStatusTracker(this)); + _syncFileStatusTracker = new SyncFileStatusTracker(this); } SyncEngine::~SyncEngine() { _goingDown = true; abort(tr("application exit", "abort reason")); - _excludedFiles.reset(); } /** @@ -289,7 +284,7 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item) void SyncEngine::startSync() { - if (!_account) { + if (!_account || !_syncOptions.isValid()) { finalize(false); return; } @@ -379,10 +374,12 @@ void SyncEngine::startSync() _progressInfo->_status = ProgressInfo::Discovery; Q_EMIT transmissionProgress(*_progressInfo); - // TODO: add a constructor to DiscoveryPhase - // pass a syncEngine object rather than copying everything to another object - _discoveryPhase.reset(new DiscoveryPhase(_account, syncOptions(), _baseUrl)); - _discoveryPhase->_excludes = _excludedFiles.get(); + if (_discoveryPhase) { + _discoveryPhase->disconnect(); + _discoveryPhase->deleteLater(); + } + _discoveryPhase = new DiscoveryPhase(_account, syncOptions(), _baseUrl, this); + _discoveryPhase->_excludes = _excludedFiles; _discoveryPhase->_statedb = _journal; _discoveryPhase->_localDir = _localPath; if (!_discoveryPhase->_localDir.endsWith(QLatin1Char('/'))) @@ -407,17 +404,17 @@ void SyncEngine::startSync() _discoveryPhase->_serverBlacklistedFiles = _account->capabilities().blacklistedFiles(); _discoveryPhase->_ignoreHiddenFiles = ignoreHiddenFiles(); - connect(_discoveryPhase.get(), &DiscoveryPhase::itemDiscovered, this, &SyncEngine::slotItemDiscovered); - connect(_discoveryPhase.get(), &DiscoveryPhase::fatalError, this, [this](const QString &errorString) { + connect(_discoveryPhase, &DiscoveryPhase::itemDiscovered, this, &SyncEngine::slotItemDiscovered); + connect(_discoveryPhase, &DiscoveryPhase::fatalError, this, [this](const QString &errorString) { Q_EMIT syncError(errorString); finalize(false); }); - connect(_discoveryPhase.get(), &DiscoveryPhase::finished, this, &SyncEngine::slotDiscoveryFinished); - connect(_discoveryPhase.get(), &DiscoveryPhase::silentlyExcluded, _syncFileStatusTracker.data(), &SyncFileStatusTracker::slotAddSilentlyExcluded); - connect(_discoveryPhase.get(), &DiscoveryPhase::excluded, _syncFileStatusTracker.data(), &SyncFileStatusTracker::slotAddSilentlyExcluded); - connect(_discoveryPhase.get(), &DiscoveryPhase::excluded, this, &SyncEngine::excluded); + connect(_discoveryPhase, &DiscoveryPhase::finished, this, &SyncEngine::slotDiscoveryFinished); + connect(_discoveryPhase, &DiscoveryPhase::silentlyExcluded, _syncFileStatusTracker, &SyncFileStatusTracker::slotAddSilentlyExcluded); + connect(_discoveryPhase, &DiscoveryPhase::excluded, _syncFileStatusTracker, &SyncFileStatusTracker::slotAddSilentlyExcluded); + connect(_discoveryPhase, &DiscoveryPhase::excluded, this, &SyncEngine::excluded); - auto discoveryJob = new ProcessDirectoryJob(_discoveryPhase.get(), PinState::AlwaysLocal, _discoveryPhase.get()); + auto discoveryJob = new ProcessDirectoryJob(_discoveryPhase, PinState::AlwaysLocal, _discoveryPhase); _discoveryPhase->startJob(discoveryJob); connect(discoveryJob, &ProcessDirectoryJob::etag, this, &SyncEngine::slotRootEtagReceived); } @@ -483,6 +480,8 @@ void SyncEngine::slotDiscoveryFinished() Q_EMIT transmissionProgress(*_progressInfo); // qCInfo(lcEngine) << "Permissions of the root folder: " << _csync_ctx->remote.root_perms.toString(); + + // why tf is this a lambda?!?!?! it just gets called after this bizarre def so why isn't it just a normal code block ffs? auto finish = [this]{ @@ -499,37 +498,7 @@ void SyncEngine::slotDiscoveryFinished() _anotherSyncNeeded = true; } - Q_ASSERT(std::is_sorted(_syncItems.begin(), _syncItems.end())); - - const auto regex = syncOptions().fileRegex(); - if (regex.isValid()) { - QSet names; - for (auto &i : _syncItems) { - if (regex.match(i->_file).hasMatch()) { - int index = -1; - QStringView ref; - do { - ref = QStringView(i->_file).mid(0, index); - names.insert(ref); - index = ref.lastIndexOf(QLatin1Char('/')); - } while (index > 0); - } - } - //std::erase_if c++20 - // https://en.cppreference.com/w/cpp/container/set/erase_if - const auto erase_if = [](auto &c, const auto &pred) { - auto old_size = c.size(); - for (auto i = c.begin(), last = c.end(); i != last;) { - if (pred(*i)) { - i = c.erase(i); - } else { - ++i; - } - } - return old_size - c.size(); - }; - erase_if(_syncItems, [&names](const SyncFileItemPtr &i) { return !names.contains(QStringView{i->_file}); }); - } + Q_ASSERT(std::is_sorted(_syncItems.begin(), _syncItems.end())); qCInfo(lcEngine) << "#### Reconcile (aboutToPropagate) ####################################################" << _duration.duration(); @@ -548,18 +517,23 @@ void SyncEngine::slotDiscoveryFinished() // do a database commit _journal->commit(QStringLiteral("post treewalk")); - _propagator = QSharedPointer::create(_account, syncOptions(), _baseUrl, _localPath, _remotePath, _journal); - connect(_propagator.data(), &OwncloudPropagator::itemCompleted, - this, &SyncEngine::slotItemCompleted); - connect(_propagator.data(), &OwncloudPropagator::progress, - this, &SyncEngine::slotProgress); - connect(_propagator.data(), &OwncloudPropagator::updateFileTotal, - this, &SyncEngine::updateFileTotal); - connect(_propagator.data(), &OwncloudPropagator::finished, this, &SyncEngine::slotPropagationFinished, Qt::QueuedConnection); - connect(_propagator.data(), &OwncloudPropagator::seenLockedFile, this, &SyncEngine::seenLockedFile); - connect(_propagator.data(), &OwncloudPropagator::insufficientLocalStorage, this, &SyncEngine::slotInsufficientLocalStorage); - connect(_propagator.data(), &OwncloudPropagator::insufficientRemoteStorage, this, &SyncEngine::slotInsufficientRemoteStorage); - connect(_propagator.data(), &OwncloudPropagator::newItem, this, &SyncEngine::slotNewItem); + // kill the old propagator and make a new one - this replaces the old QSharedPointer call to create + // it is not clear to me why the propagator needs to be killed on each run, look into some kind of internal reset + // so we can reuse the original member instance + if (_propagator) { + _propagator->disconnect(); + _propagator->deleteLater(); + } + _propagator = new OwncloudPropagator(_account, syncOptions(), _baseUrl, _localPath, _remotePath, _journal, this); + _propagator->setMoveToTrash(_moveToTrash); + connect(_propagator, &OwncloudPropagator::itemCompleted, this, &SyncEngine::slotItemCompleted); + connect(_propagator, &OwncloudPropagator::progress, this, &SyncEngine::slotProgress); + connect(_propagator, &OwncloudPropagator::updateFileTotal, this, &SyncEngine::updateFileTotal); + connect(_propagator, &OwncloudPropagator::finished, this, &SyncEngine::slotPropagationFinished, Qt::QueuedConnection); + connect(_propagator, &OwncloudPropagator::seenLockedFile, this, &SyncEngine::seenLockedFile); + connect(_propagator, &OwncloudPropagator::insufficientLocalStorage, this, &SyncEngine::slotInsufficientLocalStorage); + connect(_propagator, &OwncloudPropagator::insufficientRemoteStorage, this, &SyncEngine::slotInsufficientRemoteStorage); + connect(_propagator, &OwncloudPropagator::newItem, this, &SyncEngine::slotNewItem); deleteStaleDownloadInfos(_syncItems); deleteStaleUploadInfos(_syncItems); @@ -628,20 +602,18 @@ void SyncEngine::slotPropagationFinished(bool success) void SyncEngine::finalize(bool success) { qCInfo(lcEngine) << "Sync run took" << _duration.duration(); - _duration.stop(); - if (_discoveryPhase) { - _discoveryPhase.release()->deleteLater(); - } - _syncRunning = false; - Q_EMIT finished(success); + if (!_goingDown) { + _duration.stop(); - // Delete the propagator only after emitting the signal. - _propagator.clear(); - _seenConflictFiles.clear(); - _uniqueErrors.clear(); - _localDiscoveryPaths.clear(); - _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly; + _syncRunning = false; + _seenConflictFiles.clear(); + _uniqueErrors.clear(); + _localDiscoveryPaths.clear(); + _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly; + // pretty sure we should not be emitting anything if the engine is already in destruction? + Q_EMIT finished(success); + } } void SyncEngine::slotProgress(const SyncFileItem &item, qint64 current) @@ -767,7 +739,9 @@ void SyncEngine::abort(const QString &reason) aborting = true; // Delete the discovery and all child jobs after ensuring // it can't finish and start the propagator - disconnect(_discoveryPhase.get(), nullptr, this, nullptr); + disconnect(_discoveryPhase, nullptr, this, nullptr); + // I have not seen a delete in this function so assume the comment is out of date, but anyway it's generally covered in startSync. + // the _discoveryPhase is a child of SyncEngine so on sync engine destruction it will just be deleted by parent child relationship } if (aborting) { qCInfo(lcEngine) << "Aborting sync, stated reason:" << reason; diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index fcfda13ae30..5292746e0f8 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -50,8 +50,9 @@ class ProcessDirectoryJob; class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject { Q_OBJECT + public: - SyncEngine(Account *account, const QUrl &baseUrl, const QString &localPath, const QString &remotePath, SyncJournalDb *journal); + SyncEngine(Account *account, const QUrl &baseUrl, const QString &localPath, const QString &remotePath, SyncJournalDb *journal, QObject *parent); ~SyncEngine() override; Q_INVOKABLE void startSync(); @@ -61,18 +62,17 @@ class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject bool isSyncRunning() const { return _syncRunning; } - const SyncOptions &syncOptions() const - { - Q_ASSERT(_syncOptions); - return *_syncOptions; - } + const SyncOptions &syncOptions() const { return _syncOptions; } void setSyncOptions(const SyncOptions &options) { + Q_ASSERT(options.isValid()); _syncOptions = options; } bool ignoreHiddenFiles() const { return _ignore_hidden_files; } void setIgnoreHiddenFiles(bool ignore) { _ignore_hidden_files = ignore; } + void setMoveToTrash(bool trashIt) { _moveToTrash = trashIt; } + bool isExcluded(QStringView filePath) const; void addManualExclude(const QString &filePath); void addExcludeList(const QString &filePath); @@ -80,7 +80,7 @@ class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject bool reloadExcludes(); void clearManualExcludes(); - SyncFileStatusTracker &syncFileStatusTracker() { return *_syncFileStatusTracker; } + SyncFileStatusTracker *syncFileStatusTracker() { return _syncFileStatusTracker; } /* Returns whether another sync is needed to complete the sync */ bool isAnotherSyncNeeded() { return _anotherSyncNeeded; } @@ -125,8 +125,6 @@ class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject /** Access the last sync run's local discovery style */ LocalDiscoveryStyle lastLocalDiscoveryStyle() const { return _lastLocalDiscoveryStyle; } - auto getPropagator() { return _propagator; } // for the test - Q_SIGNALS: // During update, before reconcile @@ -203,23 +201,28 @@ private Q_SLOTS: SyncFileItemSet _syncItems; QPointer _account; + const QUrl _baseUrl; bool _needsUpdate; bool _syncRunning; QString _localPath; QString _remotePath; QString _remoteRootEtag; - SyncJournalDb *_journal; - std::unique_ptr _discoveryPhase; - QSharedPointer _propagator; + + // this is owned by the folder + QPointer _journal; + + // both of these are "rebuilt" on every sync + DiscoveryPhase *_discoveryPhase = nullptr; + OwncloudPropagator *_propagator = nullptr; // List of all files with conflicts QSet _seenConflictFiles; - QScopedPointer _progressInfo; + ProgressInfo *_progressInfo = nullptr; - std::unique_ptr _excludedFiles; - QScopedPointer _syncFileStatusTracker; + ExcludedFiles *_excludedFiles = nullptr; + SyncFileStatusTracker *_syncFileStatusTracker = nullptr; Utility::ChronoElapsedTimer _duration; /** @@ -229,8 +232,10 @@ private Q_SLOTS: // If ignored files should be ignored bool _ignore_hidden_files = false; + // should deleted files be moved to trash + bool _moveToTrash = false; - std::optional _syncOptions; + SyncOptions _syncOptions; bool _anotherSyncNeeded = false; @@ -250,3 +255,10 @@ private Q_SLOTS: bool _goingDown = false; }; } + +static const int syncFileItemTypeId = qRegisterMetaType("SyncFileItem"); +static const int syncFileItemPtrTypeId = qRegisterMetaType("SyncFileItemPtr"); +static const int syncFileItemStatusTypeId = qRegisterMetaType("SyncFileItem::Status"); +static const int syncFileStatusTypeId = qRegisterMetaType("SyncFileStatus"); +static const int syncFileItemSetTypeId = qRegisterMetaType("SyncFileItemSet"); +static const int syncFileItemDirectionTypeId = qRegisterMetaType("SyncFileItem::Direction"); diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index 7abd6847670..ad211a046bf 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -83,7 +83,8 @@ static inline bool hasExcludedStatus(const SyncFileItem &item) } SyncFileStatusTracker::SyncFileStatusTracker(SyncEngine *syncEngine) - : _syncEngine(syncEngine) + : QObject(syncEngine) + , _syncEngine(syncEngine) , _caseSensitivity(Utility::fsCaseSensitivity()) { connect(syncEngine, &SyncEngine::aboutToPropagate, diff --git a/src/libsync/syncfilestatustracker.h b/src/libsync/syncfilestatustracker.h index 99652b00a02..b240d85aa41 100644 --- a/src/libsync/syncfilestatustracker.h +++ b/src/libsync/syncfilestatustracker.h @@ -71,6 +71,7 @@ private Q_SLOTS: void incSyncCountAndEmitStatusChanged(const QString &relativePath, SharedFlag sharedState); void decSyncCountAndEmitStatusChanged(const QString &relativePath, SharedFlag sharedState); + // also the parent - maybe make this a qpointer regardless? SyncEngine *_syncEngine; ProblemsMap _syncProblems; diff --git a/src/libsync/syncoptions.cpp b/src/libsync/syncoptions.cpp index 4e57a4d8fad..39ed5a8047a 100644 --- a/src/libsync/syncoptions.cpp +++ b/src/libsync/syncoptions.cpp @@ -19,7 +19,7 @@ using namespace OCC; -SyncOptions::SyncOptions(QSharedPointer vfs) +SyncOptions::SyncOptions(Vfs *vfs) : _vfs(vfs) { } @@ -28,27 +28,7 @@ SyncOptions::~SyncOptions() { } -void SyncOptions::fillFromEnvironmentVariables() +bool SyncOptions::isValid() const { - int maxParallel = qEnvironmentVariableIntValue("OWNCLOUD_MAX_PARALLEL"); - if (maxParallel > 0) - _parallelNetworkJobs = maxParallel; -} - -QRegularExpression SyncOptions::fileRegex() const -{ - return _fileRegex; -} - -void SyncOptions::setFilePattern(const QString &pattern) -{ - // full match or a path ending with this pattern - setPathPattern(QStringLiteral("(^|/|\\\\)") + pattern + QLatin1Char('$')); -} - -void SyncOptions::setPathPattern(const QString &pattern) -{ - _fileRegex.setPatternOptions( - Utility::fsCasePreservingButCaseInsensitive() ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption); - _fileRegex.setPattern(pattern); + return !_vfs.isNull(); } diff --git a/src/libsync/syncoptions.h b/src/libsync/syncoptions.h index 4d60efc12c7..33eff99d699 100644 --- a/src/libsync/syncoptions.h +++ b/src/libsync/syncoptions.h @@ -33,43 +33,33 @@ namespace OCC { class OWNCLOUDSYNC_EXPORT SyncOptions { public: - explicit SyncOptions(QSharedPointer vfs); + // the VFS pointer must be stored in a QPointer as it may go out of scope from "above" + // it's owned by the Folder so if the folder dies, so does the vfs pointer. + // use isValid to see if the options should still be used, or test the vfs pointer directly to see if it's null or not. + explicit SyncOptions(Vfs *vfs = nullptr); ~SyncOptions(); + Vfs *vfs() const { return _vfs; } + /** If remotely deleted files are needed to move to trash */ - bool _moveFilesToTrash = false; + // bool _moveFilesToTrash = false; - /** Create a virtual file for new files instead of downloading. May not be null */ - QSharedPointer _vfs; /** The maximum number of active jobs in parallel */ int _parallelNetworkJobs = 6; - /** Reads settings from env vars where available. */ - void fillFromEnvironmentVariables(); - - /** A regular expression to match file names - * If no pattern is provided the default is an invalid regular expression. - */ - QRegularExpression fileRegex() const; - /** - * A pattern like *.txt, matching only file names + * @brief isValid indicates if the options are complete + * @return true if vfs is non-null, else false */ - void setFilePattern(const QString &pattern); - - /** - * A pattern like /own.*\/.*txt matching the full path - */ - void setPathPattern(const QString &pattern); - + bool isValid() const; private: - /** - * Only sync files that mathc the expression - * Invalid pattern by default. + /** Create a virtual file for new files instead of downloading. If vfs is null, isValid will return false indicating you should not use it + * implementation note: of course you can pass a nullptr to the ctr or it can be deleted from above so this is the "sanest" way to + * handle it. */ - QRegularExpression _fileRegex = QRegularExpression(QStringLiteral("(")); + QPointer _vfs; }; } diff --git a/test/testblacklist.cpp b/test/testblacklist.cpp index 554d7eed78d..027b73330d9 100644 --- a/test/testblacklist.cpp +++ b/test/testblacklist.cpp @@ -14,7 +14,7 @@ using namespace OCC; SyncJournalFileRecord journalRecord(FakeFolder &folder, const QByteArray &path) { SyncJournalFileRecord rec; - folder.syncJournal().getFileRecord(path, &rec); + folder.syncJournal()->getFileRecord(path, &rec); return rec; } @@ -98,7 +98,7 @@ private Q_SLOTS: QCOMPARE(it->_status, SyncFileItem::NormalError); // initial error visible QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW); - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); QVERIFY(entry.isValid()); QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal); QCOMPARE(entry._retryCount, 1); @@ -120,7 +120,7 @@ private Q_SLOTS: QCOMPARE(it->_status, SyncFileItem::BlacklistedError); QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_IGNORE); // no retry happened! - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); QVERIFY(entry.isValid()); QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal); QCOMPARE(entry._retryCount, 1); @@ -135,10 +135,10 @@ private Q_SLOTS: // Let's expire the blacklist entry to verify it gets retried { - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); entry._ignoreDuration = 1; entry._lastTryTime -= 1; - fakeFolder.syncJournal().setErrorBlacklistEntry(entry); + fakeFolder.syncJournal()->setErrorBlacklistEntry(entry); } QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); { @@ -147,7 +147,7 @@ private Q_SLOTS: QCOMPARE(it->_status, SyncFileItem::BlacklistedError); // blacklisted as it's just a retry QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW); // retry! - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); QVERIFY(entry.isValid()); QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal); QCOMPARE(entry._retryCount, 2); @@ -173,7 +173,7 @@ private Q_SLOTS: QCOMPARE(it->_status, SyncFileItem::BlacklistedError); QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW); // retry! - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); QVERIFY(entry.isValid()); QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal); QCOMPARE(entry._retryCount, 3); @@ -189,10 +189,10 @@ private Q_SLOTS: // When the error goes away and the item is retried, the sync succeeds fakeFolder.serverErrorPaths().clear(); { - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); entry._ignoreDuration = 1; entry._lastTryTime -= 1; - fakeFolder.syncJournal().setErrorBlacklistEntry(entry); + fakeFolder.syncJournal()->setErrorBlacklistEntry(entry); } QVERIFY(fakeFolder.applyLocalModificationsAndSync()); { @@ -201,7 +201,7 @@ private Q_SLOTS: QCOMPARE(it->_status, SyncFileItem::Success); QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW); - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); QVERIFY(!entry.isValid()); QCOMPARE(counter, 4); diff --git a/test/testdatabaseerror.cpp b/test/testdatabaseerror.cpp index ba97aad74b9..1073e724600 100644 --- a/test/testdatabaseerror.cpp +++ b/test/testdatabaseerror.cpp @@ -75,14 +75,14 @@ private Q_SLOTS: fakeFolder.localModifier().rename(QStringLiteral("C"), QStringLiteral("NewDir/C")); // Set the counter - fakeFolder.syncJournal().autotestFailCounter = count; + fakeFolder.syncJournal()->autotestFailCounter = count; // run the sync bool result = fakeFolder.applyLocalModificationsAndSync(); qInfo() << "Result of iteration" << count << "was" << result; - if (fakeFolder.syncJournal().autotestFailCounter >= 0) { + if (fakeFolder.syncJournal()->autotestFailCounter >= 0) { // No error was thrown, we are finished QVERIFY(result); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -91,9 +91,9 @@ private Q_SLOTS: } if (!result) { - fakeFolder.syncJournal().autotestFailCounter = -1; + fakeFolder.syncJournal()->autotestFailCounter = -1; // esnure we actually sync and are not blocked by ignored files... - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); // Try again QVERIFY(fakeFolder.syncOnce()); } diff --git a/test/testdownload.cpp b/test/testdownload.cpp index 0dfcab24642..1d552ee06cf 100644 --- a/test/testdownload.cpp +++ b/test/testdownload.cpp @@ -89,8 +89,8 @@ private Q_SLOTS: QFETCH_GLOBAL(bool, filesAreDehydrated); FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - fakeFolder.syncEngine().setIgnoreHiddenFiles(true); - QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + fakeFolder.syncEngine()->setIgnoreHiddenFiles(true); + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); auto size = 30_MiB; fakeFolder.remoteModifier().insert(QStringLiteral("A/a0"), size); @@ -108,7 +108,7 @@ private Q_SLOTS: QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); // The sync should fail because not all the files were downloaded QCOMPARE(getItem(completeSpy, QStringLiteral("A/a0"))->_status, SyncFileItem::SoftError); QCOMPARE(getItem(completeSpy, QStringLiteral("A/a0"))->_errorString, QStringLiteral("The file could not be downloaded completely.")); - QVERIFY(fakeFolder.syncEngine().isAnotherSyncNeeded()); + QVERIFY(fakeFolder.syncEngine()->isAnotherSyncNeeded()); // Now, we need to restart, this time, it should resume. QByteArray rangeRequest; @@ -127,7 +127,7 @@ private Q_SLOTS: } return nullptr; }); - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // now this should succeed QCOMPARE(rangeRequest, QByteArrayLiteral("bytes=") + QByteArray::number(stopAfter) + '-'); QCOMPARE(rangeReply, QByteArrayLiteral("bytes ") + QByteArray::number(stopAfter) + '-'); @@ -147,8 +147,8 @@ private Q_SLOTS: QFETCH_GLOBAL(bool, filesAreDehydrated); FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - fakeFolder.syncEngine().setIgnoreHiddenFiles(true); - QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + fakeFolder.syncEngine()->setIgnoreHiddenFiles(true); + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); constexpr auto size = 30_MiB; fakeFolder.remoteModifier().insert(QStringLiteral("A/a0"), size); @@ -166,7 +166,7 @@ private Q_SLOTS: QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); // The sync should fail because not all the files were downloaded QCOMPARE(getItem(completeSpy, QStringLiteral("A/a0"))->_status, SyncFileItem::SoftError); QCOMPARE(getItem(completeSpy, QStringLiteral("A/a0"))->_errorString, QStringLiteral("The file could not be downloaded completely.")); - QVERIFY(fakeFolder.syncEngine().isAnotherSyncNeeded()); + QVERIFY(fakeFolder.syncEngine()->isAnotherSyncNeeded()); QByteArray ranges; fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { @@ -175,9 +175,9 @@ private Q_SLOTS: } return nullptr; }); - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); // perform a partial sync - fakeFolder.syncEngine().setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem, {}); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem, {}); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // now this should succeed QCOMPARE(ranges, QByteArray("bytes=" + QByteArray::number(stopAfter) + "-")); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -191,8 +191,8 @@ private Q_SLOTS: QFETCH_GLOBAL(bool, filesAreDehydrated); FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - fakeFolder.syncEngine().setIgnoreHiddenFiles(true); - QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + fakeFolder.syncEngine()->setIgnoreHiddenFiles(true); + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); const auto size = 3_MiB + 500_KiB; fakeFolder.remoteModifier().insert(QStringLiteral("A/broken"), size); @@ -214,9 +214,9 @@ private Q_SLOTS: }); bool timedOut = false; - QTimer::singleShot(10s, &fakeFolder.syncEngine(), [&]() { + QTimer::singleShot(10s, fakeFolder.syncEngine(), [&]() { timedOut = true; - fakeFolder.syncEngine().abort({}); + fakeFolder.syncEngine()->abort({}); }); if (filesAreDehydrated) { QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // Success, because files are never downloaded @@ -248,7 +248,7 @@ private Q_SLOTS: return nullptr; }); - QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); if (filesAreDehydrated) { QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // Success, because files are never downloaded } else { @@ -295,7 +295,7 @@ private Q_SLOTS: resendActual = 0; resendExpected = 10; - QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(resendActual, 6); // AbstractNetworkJob::MaxRetryCount + 1 QCOMPARE(getItem(completeSpy, QStringLiteral("A/resendme"))->_status, SyncFileItem::NormalError); diff --git a/test/testfolderman.cpp b/test/testfolderman.cpp index 2a0f234cafa..0576f49cd65 100644 --- a/test/testfolderman.cpp +++ b/test/testfolderman.cpp @@ -174,6 +174,7 @@ private Q_SLOTS: QDir dir2(dir.path()); // Folder in config and on disk: + // todo: where is this added "to the config" and what is the "config" we are even talking about here? QVERIFY(dir2.mkpath(QStringLiteral("sub/ownCloud1/folder/f"))); // Folders only on disk, not in configuration: QVERIFY(dir2.mkpath(QStringLiteral("ownCloud"))); @@ -186,13 +187,24 @@ private Q_SLOTS: auto newAccountState = createDummyAccount(); FolderMan *folderman = TestUtils::folderMan(); - // Add folder that is in the configuration, AND on disk: + + // todo: addFolder needs to be made private to FolderMan because there are other important impls "around" it depending on how the folder + // is being created. + // The only reason it's not currently private is because it's used in the tests, which is dubious. + // AT THE VERY LEAST, the folder needs to exist on disk *before* calling addFolder and with new refactoring, addFolder will fail if that is not the case + // historically, calling addFolder without an existing local dir resulted in a hideously broken Folder instance that is just waiting to crash ;) + QVERIFY(dir2.mkpath(QStringLiteral("sub/ownCloud/"))); QVERIFY(folderman->addFolder( newAccountState.get(), TestUtils::createDummyFolderDefinition(newAccountState->account(), dirPath + QStringLiteral("/sub/ownCloud/")))); - // Add folder that is in the configuration, not on disk: + + QVERIFY(dir2.mkpath(QStringLiteral("ownCloud (2)/"))); QVERIFY(folderman->addFolder( newAccountState.get(), TestUtils::createDummyFolderDefinition(newAccountState->account(), dirPath + QStringLiteral("/ownCloud (2)/")))); + // Test todo: verify that addFolder with path that has no existing local folder fails, and do some findGoodPathForNewSyncFolder tests around + // that too? With new updates, the folder will not be created, hence will not exist in the FolderMan folder container(s) which does change + // the results of the function under test, afaik + // TEST const auto folderType = FolderMan::NewFolderType::SpacesFolder; const auto uuid = newAccountState->account()->uuid(); @@ -210,6 +222,7 @@ private Q_SLOTS: // REMOVE ownCloud2 from the filesystem, but keep a folder sync'ed to it. // We should still not suggest this folder as a new folder. + // todo: add a verify here to check if the folder exists in the first place before removing it QDir(dirPath + QStringLiteral("/ownCloud (2)/")).removeRecursively(); QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("ownCloud"), folderType, uuid), dirPath + QStringLiteral("/ownCloud (3)")); QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("ownCloud2"), folderType, uuid), diff --git a/test/testfolderwatcher.cpp b/test/testfolderwatcher.cpp index 62248a600d1..d3afba4c13d 100644 --- a/test/testfolderwatcher.cpp +++ b/test/testfolderwatcher.cpp @@ -21,6 +21,9 @@ class FolderWatcherForTests : public OCC::FolderWatcher public: using OCC::FolderWatcher::FolderWatcher; + FolderWatcherForTests() + : FolderWatcher(nullptr) { }; + bool pathIsIgnored(const QString &) const override { return false; } }; @@ -134,7 +137,7 @@ class TestFolderWatcher : public QObject TestUtils::writeRandomFile(_rootPath + QStringLiteral("/a2/renamefile")); TestUtils::writeRandomFile(_rootPath + QStringLiteral("/a1/movefile")); - _watcher.reset(new FolderWatcherForTests); + _watcher.reset(new FolderWatcherForTests()); _watcher->init(_rootPath); _pathChangedSpy.reset(new QSignalSpy(_watcher.data(), &FolderWatcher::pathChanged)); } diff --git a/test/testlocaldiscovery.cpp b/test/testlocaldiscovery.cpp index 81a39b5d940..7b9d3543822 100644 --- a/test/testlocaldiscovery.cpp +++ b/test/testlocaldiscovery.cpp @@ -8,8 +8,8 @@ #include "testutils/syncenginetestutils.h" #include "testutils/testutils.h" -#include #include +#include #include @@ -45,9 +45,9 @@ private Q_SLOTS: FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - LocalDiscoveryTracker tracker; - connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); - connect(&fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); + LocalDiscoveryTracker tracker(nullptr); + connect(fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); + connect(fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); // More subdirectories are useful for testing fakeFolder.localModifier().mkdir(QStringLiteral("A/X")); @@ -71,7 +71,7 @@ private Q_SLOTS: fakeFolder.remoteModifier().appendByte(QStringLiteral("C/c1")); tracker.addTouchedPath(QStringLiteral("A/X")); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); @@ -80,12 +80,12 @@ private Q_SLOTS: QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("A/Y/y2"))); QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("B/b3"))); QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("C/c3"))); - QCOMPARE(fakeFolder.syncEngine().lastLocalDiscoveryStyle(), LocalDiscoveryStyle::DatabaseAndFilesystem); + QCOMPARE(fakeFolder.syncEngine()->lastLocalDiscoveryStyle(), LocalDiscoveryStyle::DatabaseAndFilesystem); QVERIFY(tracker.localDiscoveryPaths().empty()); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.syncEngine().lastLocalDiscoveryStyle(), LocalDiscoveryStyle::FilesystemOnly); + QCOMPARE(fakeFolder.syncEngine()->lastLocalDiscoveryStyle(), LocalDiscoveryStyle::FilesystemOnly); QVERIFY(tracker.localDiscoveryPaths().empty()); } @@ -95,45 +95,43 @@ private Q_SLOTS: QFETCH_GLOBAL(bool, filesAreDehydrated); FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - auto &engine = fakeFolder.syncEngine(); + auto engine = fakeFolder.syncEngine(); - QVERIFY(engine.shouldDiscoverLocally(QString())); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("A"))); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("A/X"))); + QVERIFY(engine->shouldDiscoverLocally(QString())); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X"))); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {QStringLiteral("A/X"), QStringLiteral("A/X space"), QStringLiteral("A/X/beta"), QStringLiteral("foo bar space/touch"), QStringLiteral("foo/"), QStringLiteral("zzz"), QStringLiteral("zzzz")}); - QVERIFY(engine.shouldDiscoverLocally(QString())); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("A"))); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("A/X"))); - QVERIFY(!engine.shouldDiscoverLocally(QStringLiteral("B"))); - QVERIFY(!engine.shouldDiscoverLocally(QStringLiteral("A B"))); - QVERIFY(!engine.shouldDiscoverLocally(QStringLiteral("B/X"))); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("foo bar space"))); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("foo"))); - QVERIFY(!engine.shouldDiscoverLocally(QStringLiteral("foo bar"))); - QVERIFY(!engine.shouldDiscoverLocally(QStringLiteral("foo bar/touch"))); + QVERIFY(engine->shouldDiscoverLocally(QString())); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("B"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("A B"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("B/X"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("foo bar space"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("foo"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("foo bar"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("foo bar/touch"))); // These are within QStringLiteral("A/X") so they should be discovered - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("A/X/alpha"))); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("A/X beta"))); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("A/X/Y"))); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("A/X space"))); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("A/X space/alpha"))); - QVERIFY(!engine.shouldDiscoverLocally(QStringLiteral("A/Xylo/foo"))); - QVERIFY(engine.shouldDiscoverLocally(QStringLiteral("zzzz/hello"))); - QVERIFY(!engine.shouldDiscoverLocally(QStringLiteral("zzza/hello"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X/alpha"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X beta"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X/Y"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X space"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X space/alpha"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("A/Xylo/foo"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("zzzz/hello"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("zzza/hello"))); QEXPECT_FAIL("", "There is a possibility of false positives if the set contains a path " "which is a prefix, and that prefix is followed by a character less than '/'", Continue); - QVERIFY(!engine.shouldDiscoverLocally(QStringLiteral("A/X o"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("A/X o"))); - fakeFolder.syncEngine().setLocalDiscoveryOptions( - LocalDiscoveryStyle::DatabaseAndFilesystem, - {}); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {}); - QVERIFY(!engine.shouldDiscoverLocally(QString())); + QVERIFY(!engine->shouldDiscoverLocally(QString())); } // Check whether item success and item failure adjusts the @@ -145,9 +143,9 @@ private Q_SLOTS: FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - LocalDiscoveryTracker tracker; - connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); - connect(&fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); + LocalDiscoveryTracker tracker(nullptr); + connect(fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); + connect(fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); auto trackerContains = [&](const char *path) { return tracker.localDiscoveryPaths().find(QString::fromLatin1(path)) != tracker.localDiscoveryPaths().end(); }; @@ -162,7 +160,7 @@ private Q_SLOTS: // We're not adding a4 as touched, it's in the same folder as a3 and will be seen. // And due to the error it should be added to the explicit list while a3 gets removed. - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); @@ -172,7 +170,7 @@ private Q_SLOTS: QVERIFY(trackerContains("A/a4")); QVERIFY(trackerContains("A/spurious")); // not removed since overall sync not successful - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly); tracker.startSyncFullDiscovery(); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); @@ -181,10 +179,10 @@ private Q_SLOTS: QVERIFY(!trackerContains("A/spurious")); // removed due to full discovery fakeFolder.serverErrorPaths().clear(); - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); tracker.addTouchedPath(QStringLiteral("A/newspurious")); // will be removed due to successful sync - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); @@ -204,9 +202,7 @@ private Q_SLOTS: fakeFolder.localModifier().insert(QStringLiteral("A/newDir/subDir/file"), 10_B); // Only "A" was modified according to the file system tracker - fakeFolder.syncEngine().setLocalDiscoveryOptions( - LocalDiscoveryStyle::DatabaseAndFilesystem, - { QStringLiteral("A") }); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {QStringLiteral("A")}); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); @@ -275,9 +271,9 @@ private Q_SLOTS: fakeFolder.remoteModifier().mkdir(QStringLiteral("P/B") + correct); fakeFolder.remoteModifier().insert(QStringLiteral("P/B") + correct + QStringLiteral("/b")); - LocalDiscoveryTracker tracker; - connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); - connect(&fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); + LocalDiscoveryTracker tracker(nullptr); + connect(fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); + connect(fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); // First sync: discover that there are files/directories on the server that are not yet synced to the local end QVERIFY(fakeFolder.applyLocalModificationsAndSync()); @@ -294,7 +290,7 @@ private Q_SLOTS: qDebug() << "*** MARK"; // Log marker to check if a PUT/DELETE shows up in the second sync // Force a full local discovery on the next sync, which forces a walk of the (local) file system, reading back names (and file sizes/mtimes/etc.)... - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {QStringLiteral("P")}); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {QStringLiteral("P")}); tracker.startSyncFullDiscovery(); // ... and start the second sync: diff --git a/test/testlockedfiles.cpp b/test/testlockedfiles.cpp index b1650c5e060..dde5d51250e 100644 --- a/test/testlockedfiles.cpp +++ b/test/testlockedfiles.cpp @@ -132,12 +132,11 @@ private Q_SLOTS: } QStringList seenLockedFiles; - connect(&fakeFolder.syncEngine(), &SyncEngine::seenLockedFile, &fakeFolder.syncEngine(), - [&](const QString &file) { seenLockedFiles.append(file); }); + connect(fakeFolder.syncEngine(), &SyncEngine::seenLockedFile, fakeFolder.syncEngine(), [&](const QString &file) { seenLockedFiles.append(file); }); - LocalDiscoveryTracker tracker; - connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); - connect(&fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); + LocalDiscoveryTracker tracker(nullptr); + connect(fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); + connect(fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); auto hasLocalDiscoveryPath = [&](const QString &path) { auto &paths = tracker.localDiscoveryPaths(); return paths.find(path) != paths.end(); @@ -151,7 +150,7 @@ private Q_SLOTS: tracker.addTouchedPath(QStringLiteral("A/a1")); auto h1 = makeHandle(fakeFolder.localPath() + QStringLiteral("A/a1"), 0); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); @@ -161,9 +160,9 @@ private Q_SLOTS: CloseHandle(h1); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -177,7 +176,7 @@ private Q_SLOTS: QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); auto h2 = makeHandle(fakeFolder.localPath() + QStringLiteral("A/a1"), 0); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); @@ -186,9 +185,9 @@ private Q_SLOTS: CloseHandle(h2); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } diff --git a/test/testpermissions.cpp b/test/testpermissions.cpp index a1e1585c15b..166ceaa407e 100644 --- a/test/testpermissions.cpp +++ b/test/testpermissions.cpp @@ -25,21 +25,21 @@ static void applyPermissionsFromName(FileInfo &info) { // Check if the expected rows in the DB are non-empty. Note that in some cases they might be, then we cannot use this function // https://github.com/owncloud/client/issues/2038 -static void assertCsyncJournalOk(SyncJournalDb &journal) +static void assertCsyncJournalOk(SyncJournalDb *journal) { // The DB is openend in locked mode: close to allow us to access. - journal.close(); + journal->close(); SqlDatabase db; - QVERIFY(db.openReadOnly(journal.databaseFilePath())); + QVERIFY(db.openReadOnly(journal->databaseFilePath())); SqlQuery q("SELECT count(*) from metadata where length(fileId) == 0", db); QVERIFY(q.exec()); QVERIFY(q.next().hasData); QCOMPARE(q.intValue(0), 0); #if defined(Q_OS_WIN) // Make sure the file does not appear in the FileInfo - FileSystem::setFileHidden(journal.databaseFilePath() + QStringLiteral("-shm"), true); + FileSystem::setFileHidden(journal->databaseFilePath() + QStringLiteral("-shm"), true); #endif - journal.allowReopen(); + journal->allowReopen(); } SyncFileItemPtr findDiscoveryItem(const SyncFileItemSet &spy, const QString &path) @@ -101,9 +101,9 @@ private Q_SLOTS: // Some of this test depends on the order of discovery. With threading // that order becomes effectively random, but we want to make sure to test // all cases and thus disable threading. - auto syncOpts = fakeFolder.syncEngine().syncOptions(); + auto syncOpts = fakeFolder.syncEngine()->syncOptions(); syncOpts._parallelNetworkJobs = 1; - fakeFolder.syncEngine().setSyncOptions(syncOpts); + fakeFolder.syncEngine()->setSyncOptions(syncOpts); const auto cannotBeModifiedSize = 133_B; const auto canBeModifiedSize = 144_B; @@ -391,9 +391,9 @@ private Q_SLOTS: // Some of this test depends on the order of discovery. With threading // that order becomes effectively random, but we want to make sure to test // all cases and thus disable threading. - auto syncOpts = fakeFolder.syncEngine().syncOptions(); + auto syncOpts = fakeFolder.syncEngine()->syncOptions(); syncOpts._parallelNetworkJobs = 1; - fakeFolder.syncEngine().setSyncOptions(syncOpts); + fakeFolder.syncEngine()->setSyncOptions(syncOpts); auto &lm = fakeFolder.localModifier(); auto &rm = fakeFolder.remoteModifier(); @@ -444,7 +444,7 @@ private Q_SLOTS: // also hook into discovery!! SyncFileItemSet discovery; - connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, this, [&discovery](auto v) { discovery = v; }); + connect(fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, this, [&discovery](auto v) { discovery = v; }); ItemCompletedSpy completeSpy(fakeFolder); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); @@ -480,11 +480,11 @@ private Q_SLOTS: QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/file"), CSYNC_INSTRUCTION_NEW)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/sub2"), CSYNC_INSTRUCTION_NEW)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/sub2/file"), CSYNC_INSTRUCTION_NEW)); - QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), true); + QCOMPARE(fakeFolder.syncEngine()->isAnotherSyncNeeded(), true); // A follow-up sync will restore allowed/file and allowed/sub2 and maintain the nocreatedir/file errors completeSpy.clear(); - QCOMPARE(fakeFolder.syncJournal().wipeErrorBlacklist(), 4); + QCOMPARE(fakeFolder.syncJournal()->wipeErrorBlacklist(), 4); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/file"), CSYNC_INSTRUCTION_ERROR)); @@ -518,9 +518,9 @@ private Q_SLOTS: // Some of this test depends on the order of discovery. With threading // that order becomes effectively random, but we want to make sure to test // all cases and thus disable threading. - auto syncOpts = fakeFolder.syncEngine().syncOptions(); + auto syncOpts = fakeFolder.syncEngine()->syncOptions(); syncOpts._parallelNetworkJobs = 1; - fakeFolder.syncEngine().setSyncOptions(syncOpts); + fakeFolder.syncEngine()->setSyncOptions(syncOpts); auto &lm = fakeFolder.localModifier(); auto &rm = fakeFolder.remoteModifier(); diff --git a/test/testremotediscovery.cpp b/test/testremotediscovery.cpp index 2ba69601682..92198a126a9 100644 --- a/test/testremotediscovery.cpp +++ b/test/testremotediscovery.cpp @@ -130,7 +130,7 @@ private Q_SLOTS: QScopedValueRollback setHttpTimeout(AbstractNetworkJob::httpTimeout, errorKind == Timeout ? 1s : 10000s); ItemCompletedSpy completeSpy(fakeFolder); - QSignalSpy errorSpy(&fakeFolder.syncEngine(), &SyncEngine::syncError); + QSignalSpy errorSpy(fakeFolder.syncEngine(), &SyncEngine::syncError); QCOMPARE(fakeFolder.applyLocalModificationsAndSync(), syncSucceeds); // The folder B should not have been sync'ed (and in particular not removed) diff --git a/test/testsyncconflict.cpp b/test/testsyncconflict.cpp index 12b3e47db25..25efa009e8c 100644 --- a/test/testsyncconflict.cpp +++ b/test/testsyncconflict.cpp @@ -63,7 +63,7 @@ bool expectAndWipeConflict(FakeFolder &fFolder, const QString &path) SyncJournalFileRecord dbRecord(FakeFolder &folder, const QString &path) { SyncJournalFileRecord record; - folder.syncJournal().getFileRecord(path, &record); + folder.syncJournal()->getFileRecord(path, &record); return record; } @@ -133,22 +133,22 @@ private Q_SLOTS: // Make conflict records ConflictRecord conflictRecord; conflictRecord.path = "A/a1"; - fakeFolder.syncJournal().setConflictRecord(conflictRecord); + fakeFolder.syncJournal()->setConflictRecord(conflictRecord); conflictRecord.path = "A/a2"; - fakeFolder.syncJournal().setConflictRecord(conflictRecord); + fakeFolder.syncJournal()->setConflictRecord(conflictRecord); // A nothing-to-sync keeps them alive QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QVERIFY(fakeFolder.syncJournal().conflictRecord("A/a1").isValid()); - QVERIFY(fakeFolder.syncJournal().conflictRecord("A/a2").isValid()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord("A/a1").isValid()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord("A/a2").isValid()); // When the file is removed, the record is removed too fakeFolder.localModifier().remove(QStringLiteral("A/a2")); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QVERIFY(fakeFolder.syncJournal().conflictRecord("A/a1").isValid()); - QVERIFY(!fakeFolder.syncJournal().conflictRecord("A/a2").isValid()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord("A/a1").isValid()); + QVERIFY(!fakeFolder.syncJournal()->conflictRecord("A/a2").isValid()); } void testConflictRecordRemoval2() @@ -185,14 +185,14 @@ private Q_SLOTS: // A nothing-to-sync keeps them alive QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(fakeFolder.syncJournal().conflictRecord(a1conflict.toUtf8()).isValid()); - QVERIFY(fakeFolder.syncJournal().conflictRecord(a2conflict.toUtf8()).isValid()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord(a1conflict.toUtf8()).isValid()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord(a2conflict.toUtf8()).isValid()); // When the file is removed, the record is removed too fakeFolder.localModifier().remove(a2conflict); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(fakeFolder.syncJournal().conflictRecord(a1conflict.toUtf8()).isValid()); - QVERIFY(!fakeFolder.syncJournal().conflictRecord(a2conflict.toUtf8()).isValid()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord(a1conflict.toUtf8()).isValid()); + QVERIFY(!fakeFolder.syncJournal()->conflictRecord(a2conflict.toUtf8()).isValid()); } } @@ -313,7 +313,7 @@ private Q_SLOTS: QCOMPARE(conflicts.size(), 3); std::sort(conflicts.begin(), conflicts.end()); - auto conflictRecords = fakeFolder.syncJournal().conflictRecordPaths(); + auto conflictRecords = fakeFolder.syncJournal()->conflictRecordPaths(); QCOMPARE(conflictRecords.size(), 3); std::sort(conflictRecords.begin(), conflictRecords.end()); @@ -373,7 +373,7 @@ private Q_SLOTS: if (filesAreDehydrated) { // the dehydrating the placeholder failed as the metadata is out of sync - QSignalSpy spy(fakeFolder.vfs().get(), &Vfs::needSync); + QSignalSpy spy(fakeFolder.vfs(), &Vfs::needSync); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); QVERIFY(spy.count() == 1); QVERIFY(fakeFolder.syncOnce()); @@ -390,7 +390,7 @@ private Q_SLOTS: QCOMPARE(conflicts.size(), 3); std::sort(conflicts.begin(), conflicts.end()); - auto conflictRecords = fakeFolder.syncJournal().conflictRecordPaths(); + auto conflictRecords = fakeFolder.syncJournal()->conflictRecordPaths(); QCOMPARE(conflictRecords.size(), 3); std::sort(conflictRecords.begin(), conflictRecords.end()); @@ -484,7 +484,7 @@ private Q_SLOTS: QDir(fakeFolder.localPath() + conflict).removeRecursively(); } - QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), true); + QCOMPARE(fakeFolder.syncEngine()->isAnotherSyncNeeded(), true); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index 17b2e47c8e8..6f48ac0ad4c 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -161,7 +161,7 @@ private Q_SLOTS: auto getDbChecksum = [&](const QString &path) { SyncJournalFileRecord record; - fakeFolder.syncJournal().getFileRecord(path, &record); + fakeFolder.syncJournal()->getFileRecord(path, &record); return record._checksumHeader; }; @@ -217,11 +217,11 @@ private Q_SLOTS: auto expectedServerState = fakeFolder.currentRemoteState(); // Remove subFolderA with selectiveSync: - fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {QStringLiteral("parentFolder/subFolderA/")}); - fakeFolder.syncEngine().journal()->schedulePathForRemoteDiscovery(QByteArrayLiteral("parentFolder/subFolderA/")); + fakeFolder.syncEngine()->journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {QStringLiteral("parentFolder/subFolderA/")}); + fakeFolder.syncEngine()->journal()->schedulePathForRemoteDiscovery(QByteArrayLiteral("parentFolder/subFolderA/")); auto getEtag = [&](const QByteArray &file) { SyncJournalFileRecord rec; - fakeFolder.syncJournal().getFileRecord(file, &rec); + fakeFolder.syncJournal()->getFileRecord(file, &rec); return rec._etag; }; QVERIFY(getEtag("parentFolder") == "_invalid_"); @@ -264,7 +264,7 @@ private Q_SLOTS: QFETCH_GLOBAL(bool, filesAreDehydrated); FakeFolder fakeFolder(FileInfo {}, vfsMode, filesAreDehydrated); - QSignalSpy finishedSpy(&fakeFolder.syncEngine(), &SyncEngine::finished); + QSignalSpy finishedSpy(fakeFolder.syncEngine(), &SyncEngine::finished); fakeFolder.serverErrorPaths().append(QStringLiteral("NewFolder")); fakeFolder.localModifier().mkdir(QStringLiteral("NewFolder")); // This should be aborted and would otherwise fail in FileInfo::create. @@ -285,7 +285,7 @@ private Q_SLOTS: QFETCH_GLOBAL(bool, filesAreDehydrated); FakeFolder fakeFolder(FileInfo {}, vfsMode, filesAreDehydrated); - QSignalSpy finishedSpy(&fakeFolder.syncEngine(), &SyncEngine::finished); + QSignalSpy finishedSpy(fakeFolder.syncEngine(), &SyncEngine::finished); fakeFolder.serverErrorPaths().append(QStringLiteral("NewFolder/foo")); fakeFolder.remoteModifier().mkdir(QStringLiteral("NewFolder")); fakeFolder.remoteModifier().insert(QStringLiteral("NewFolder/foo")); @@ -297,7 +297,7 @@ private Q_SLOTS: } SyncJournalFileRecord rec; - fakeFolder.syncJournal().getFileRecord(QByteArrayLiteral("NewFolder"), &rec); + fakeFolder.syncJournal()->getFileRecord(QByteArrayLiteral("NewFolder"), &rec); QVERIFY(rec.isValid()); if (filesAreDehydrated) { // No error, failure occurs only with a GET, so etag should be valid (i.e. NOT invalid): @@ -439,7 +439,7 @@ private Q_SLOTS: // check that mtime in journal and filesystem agree QString a1path = fakeFolder.localPath() + QStringLiteral("A/a1"); SyncJournalFileRecord a1record; - fakeFolder.syncJournal().getFileRecord(QByteArray("A/a1"), &a1record); + fakeFolder.syncJournal()->getFileRecord(QByteArray("A/a1"), &a1record); QCOMPARE(a1record._modtime, (qint64)FileSystem::getModTime(a1path)); // Extra sync reads from db, no difference @@ -490,7 +490,7 @@ private Q_SLOTS: fakeFolder.remoteModifier().appendByte(QStringLiteral("C/c1")); fakeFolder.remoteModifier().setModTime(QStringLiteral("C/c1"), changedMtime2); - connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, [&](const SyncFileItemSet &items) { + connect(fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, [&](const SyncFileItemSet &items) { SyncFileItemPtr a1, b1, c1; for (auto &item : items) { if (item->_file == QLatin1String("A/a1")) @@ -544,9 +544,9 @@ private Q_SLOTS: FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); // Disable parallel uploads - SyncOptions syncOptions = fakeFolder.syncEngine().syncOptions(); + SyncOptions syncOptions = fakeFolder.syncEngine()->syncOptions(); syncOptions._parallelNetworkJobs = 0; - fakeFolder.syncEngine().setSyncOptions(syncOptions); + fakeFolder.syncEngine()->setSyncOptions(syncOptions); // Produce an error based on upload size int remoteQuota = 1000; @@ -627,7 +627,7 @@ private Q_SLOTS: // Good OC-Checksum checksumValue = "SHA1:19b1928d58a2030d08023f3d7054516dbc186f20"; // printf 'A%.0s' {1..16} | sha1sum - - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); checksumValue = QByteArray(); @@ -646,7 +646,7 @@ private Q_SLOTS: // Good Content-MD5 contentMd5Value = "d8a73157ce10cd94a91c2079fc9a92c8"; // printf 'A%.0s' {1..16} | md5sum - - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -666,7 +666,7 @@ private Q_SLOTS: QVERIFY(!syncSucceeded); } contentMd5Value.clear(); - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -682,7 +682,7 @@ private Q_SLOTS: QVERIFY(!syncSucceeded); } checksumValue = "Unsupported:XXXX SHA1:19b1928d58a2030d08023f3d7054516dbc186f20 Invalid:XxX"; - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // The supported SHA1 checksum is valid now, so the file are downloaded QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } @@ -734,15 +734,15 @@ private Q_SLOTS: return QFileInfo::exists(fakeFolder.localPath() + name); }; - fakeFolder.syncEngine().setIgnoreHiddenFiles(true); + fakeFolder.syncEngine()->setIgnoreHiddenFiles(true); fakeFolder.remoteModifier().insert(QStringLiteral("A/.hidden")); fakeFolder.localModifier().insert(QStringLiteral("B/.hidden")); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QVERIFY(!localFileExists(QStringLiteral("A/.hidden"))); QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("B/.hidden"))); - fakeFolder.syncEngine().setIgnoreHiddenFiles(false); - fakeFolder.syncJournal().forceRemoteDiscoveryNextSync(); + fakeFolder.syncEngine()->setIgnoreHiddenFiles(false); + fakeFolder.syncJournal()->forceRemoteDiscoveryNextSync(); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QVERIFY(localFileExists(QStringLiteral("A/.hidden"))); QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("B/.hidden"))); @@ -755,8 +755,9 @@ private Q_SLOTS: QFETCH_GLOBAL(bool, filesAreDehydrated); FakeFolder fakeFolder(FileInfo {}, vfsMode, filesAreDehydrated); - SyncOptions options = fakeFolder.syncEngine().syncOptions(); - fakeFolder.syncEngine().setSyncOptions(options); + // I'm pretty sure the next two lines are COMPLETELY USELESS + // SyncOptions options = fakeFolder.syncEngine()->syncOptions(); + // fakeFolder.syncEngine()->setSyncOptions(options); auto cap = TestUtils::testCapabilities(); // unset chunking v1 cap.remove(QStringLiteral("dav")); @@ -777,8 +778,8 @@ private Q_SLOTS: fakeFolder.localModifier().insert(QStringLiteral("file3"), 1_MiB, 'W'); // wait until the sync engine is ready // wait a second and abort - connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, &fakeFolder.syncEngine(), - [&]() { QTimer::singleShot(1s, &fakeFolder.syncEngine(), [&]() { fakeFolder.syncEngine().abort({}); }); }); + connect(fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, fakeFolder.syncEngine(), + [&]() { QTimer::singleShot(1s, fakeFolder.syncEngine(), [&]() { fakeFolder.syncEngine()->abort({}); }); }); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(counter->nPUT, 3); @@ -803,7 +804,7 @@ private Q_SLOTS: QCOMPARE(QFileInfo(fakeFolder.localPath() + QStringLiteral("A/a1")).permissions(), perm); QCOMPARE(QFileInfo(fakeFolder.localPath() + QStringLiteral("A/a2")).permissions(), perm); - const QString conflictName = QString::fromUtf8(fakeFolder.syncJournal().conflictRecord(fakeFolder.syncJournal().conflictRecordPaths().first()).path); + const QString conflictName = QString::fromUtf8(fakeFolder.syncJournal()->conflictRecord(fakeFolder.syncJournal()->conflictRecordPaths().first()).path); QVERIFY(conflictName.contains(QStringLiteral("A/a2"))); QCOMPARE(QFileInfo(fakeFolder.localPath() + conflictName).permissions(), perm); } diff --git a/test/testsyncfilestatustracker.cpp b/test/testsyncfilestatustracker.cpp index 93c755e62ba..ef43df98eb9 100644 --- a/test/testsyncfilestatustracker.cpp +++ b/test/testsyncfilestatustracker.cpp @@ -13,17 +13,18 @@ using namespace OCC; class StatusPushSpy : public QSignalSpy { - SyncEngine &_syncEngine; + SyncEngine *_syncEngine; + public: - StatusPushSpy(SyncEngine &syncEngine) - : QSignalSpy(&syncEngine.syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged) + StatusPushSpy(SyncEngine *syncEngine) + : QSignalSpy(syncEngine->syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged) , _syncEngine(syncEngine) { } SyncFileStatus statusOf(const QString &relativePath) const { - const QFileInfo file(_syncEngine.localPath(), relativePath); + const QFileInfo file(_syncEngine->localPath(), relativePath); // Start from the end to get the latest status for (auto it = crbegin(); it != crend(); ++it) { const auto info = QFileInfo(it->at(0).toString()); @@ -35,8 +36,8 @@ class StatusPushSpy : public QSignalSpy } bool statusEmittedBefore(const QString &firstPath, const QString &secondPath) const { - QFileInfo firstFile(_syncEngine.localPath(), firstPath); - QFileInfo secondFile(_syncEngine.localPath(), secondPath); + QFileInfo firstFile(_syncEngine->localPath(), firstPath); + QFileInfo secondFile(_syncEngine->localPath(), secondPath); // Start from the end to get the latest status int i = size() - 1; for (; i >= 0; --i) { @@ -69,7 +70,7 @@ class TestSyncFileStatusTracker : public QObject QString filePath = it.next().mid(root.size()); auto pushedStatus = statusSpy.statusOf(filePath); if (pushedStatus != SyncFileStatus()) { - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(filePath), pushedStatus); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(filePath), pushedStatus); } } } @@ -90,10 +91,10 @@ private Q_SLOTS: QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusSync)); QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync)); QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("C/c2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("C/c2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); statusSpy.clear(); fakeFolder.execUntilFinished(); @@ -122,10 +123,10 @@ private Q_SLOTS: QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusSync)); QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync)); QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c0")), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); statusSpy.clear(); fakeFolder.execUntilFinished(); @@ -215,9 +216,9 @@ private Q_SLOTS: // Discovered as remotely removed, pending for local removal. QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusSync)); QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("C/c2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("C/c2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); statusSpy.clear(); fakeFolder.execUntilFinished(); @@ -231,8 +232,8 @@ private Q_SLOTS: void warningStatusForExcludedFile() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().addManualExclude(QStringLiteral("A/a1")); - fakeFolder.syncEngine().addManualExclude(QStringLiteral("B")); + fakeFolder.syncEngine()->addManualExclude(QStringLiteral("A/a1")); + fakeFolder.syncEngine()->addManualExclude(QStringLiteral("B")); fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); fakeFolder.localModifier().appendByte(QStringLiteral("B/b1")); QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); @@ -254,12 +255,12 @@ private Q_SLOTS: QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusExcluded)); QEXPECT_FAIL("", "csync will stop at ignored directories without traversing children, so we don't currently push the status for newly ignored children of an ignored directory.", Continue); QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusExcluded)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); statusSpy.clear(); // Clears the exclude expr above - fakeFolder.syncEngine().clearManualExcludes(); + fakeFolder.syncEngine()->clearManualExcludes(); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); @@ -282,23 +283,23 @@ private Q_SLOTS: void warningStatusForExcludedFile_CasePreserving() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().addManualExclude(QStringLiteral("B")); + fakeFolder.syncEngine()->addManualExclude(QStringLiteral("B")); fakeFolder.serverErrorPaths().append(QStringLiteral("A/a1")); fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); fakeFolder.syncOnce(); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusExcluded)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusExcluded)); // Should still get the status for different casing on macOS and Windows. - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("a")), + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("a")), SyncFileStatus(Utility::fsCasePreservingButCaseInsensitive() ? SyncFileStatus::StatusWarning : SyncFileStatus::StatusNone)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/A1")), + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/A1")), SyncFileStatus(Utility::fsCasePreservingButCaseInsensitive() ? SyncFileStatus::StatusError : SyncFileStatus::StatusNone)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("b")), + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("b")), SyncFileStatus(Utility::fsCasePreservingButCaseInsensitive() ? SyncFileStatus::StatusExcluded : SyncFileStatus::StatusNone)); } @@ -326,7 +327,7 @@ private Q_SLOTS: QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning)); QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError)); statusSpy.clear(); @@ -348,7 +349,7 @@ private Q_SLOTS: QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning)); QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError)); statusSpy.clear(); @@ -374,7 +375,7 @@ private Q_SLOTS: QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning)); QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError)); QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); @@ -382,8 +383,8 @@ private Q_SLOTS: statusSpy.clear(); // Another sync after clearing the blacklist entry, everything should return to order. - fakeFolder.syncEngine().journal()->wipeErrorBlacklistEntry(QStringLiteral("A/a1")); - fakeFolder.syncEngine().journal()->wipeErrorBlacklistEntry(QStringLiteral("B/b0")); + fakeFolder.syncEngine()->journal()->wipeErrorBlacklistEntry(QStringLiteral("A/a1")); + fakeFolder.syncEngine()->journal()->wipeErrorBlacklistEntry(QStringLiteral("B/b0")); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); @@ -418,18 +419,18 @@ private Q_SLOTS: fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); // The SyncFileStatusTraker won't push any status for all of them, test with a pull. - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/a")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); fakeFolder.execUntilFinished(); // We use string matching for paths in the implementation, // an error should affect only parents and not every path that starts with the problem path. - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(QStringLiteral("A/a")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(fakeFolder.syncEngine()->syncFileStatusTracker()->fileStatus(QStringLiteral("A/a")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); } // Even for status pushes immediately following each other, macOS diff --git a/test/testsyncjournaldb.cpp b/test/testsyncjournaldb.cpp index a61c6831a6f..c098df6a8b5 100644 --- a/test/testsyncjournaldb.cpp +++ b/test/testsyncjournaldb.cpp @@ -21,7 +21,7 @@ class TestSyncJournalDB : public QObject public: TestSyncJournalDB() - : _db((_tempDir.path() + QStringLiteral("/sync.db"))) + : _db((_tempDir.path() + QStringLiteral("/sync.db")), nullptr) { QVERIFY(_tempDir.isValid()); } diff --git a/test/testsyncmove.cpp b/test/testsyncmove.cpp index 9d38221164b..f2ab00cc233 100644 --- a/test/testsyncmove.cpp +++ b/test/testsyncmove.cpp @@ -116,8 +116,8 @@ private Q_SLOTS: auto expectedServerState = fakeFolder.currentRemoteState(); // Remove subFolderA with selectiveSync: - fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {QStringLiteral("parentFolder/subFolderA/")}); - fakeFolder.syncEngine().journal()->schedulePathForRemoteDiscovery(QByteArrayLiteral("parentFolder/subFolderA/")); + fakeFolder.syncEngine()->journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {QStringLiteral("parentFolder/subFolderA/")}); + fakeFolder.syncEngine()->journal()->schedulePathForRemoteDiscovery(QByteArrayLiteral("parentFolder/subFolderA/")); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); @@ -705,7 +705,7 @@ private Q_SLOTS: ItemCompletedSpy completeSpy(fakeFolder); if (filesAreDehydrated) { // the dehydrating the placeholder failed as the metadata is out of sync - QSignalSpy spy(fakeFolder.vfs().get(), &Vfs::needSync); + QSignalSpy spy(fakeFolder.vfs(), &Vfs::needSync); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); QVERIFY(spy.count() == 1); QVERIFY(fakeFolder.syncOnce()); @@ -1005,18 +1005,19 @@ private Q_SLOTS: const QString src = QStringLiteral("folder/folderA/file.txt"); const QString dest = QStringLiteral("folder/folderB/file.txt"); FakeFolder fakeFolder{ FileInfo{ QString(), { FileInfo{ QStringLiteral("folder"), { FileInfo{ QStringLiteral("folderA"), { { QStringLiteral("file.txt"), 400 } } }, QStringLiteral("folderB") } } } } }; - auto syncOpts = fakeFolder.syncEngine().syncOptions(); + auto syncOpts = fakeFolder.syncEngine()->syncOptions(); syncOpts._parallelNetworkJobs = 0; - fakeFolder.syncEngine().setSyncOptions(syncOpts); + fakeFolder.syncEngine()->setSyncOptions(syncOpts); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); if (vfsMode != Vfs::Off) { - auto vfs = QSharedPointer(VfsPluginManager::instance().createVfsFromPlugin(vfsMode).release()); + auto vfs = VfsPluginManager::instance().createVfsFromPlugin(vfsMode, &fakeFolder); QVERIFY(vfs); + // todo: this is going to kill the parallel jobs count set above - I don't know if it matters so need to check fakeFolder.switchToVfs(vfs); - fakeFolder.syncJournal().internalPinStates().setForPath("", PinState::OnlineOnly); + fakeFolder.syncJournal()->internalPinStates().setForPath("", PinState::OnlineOnly); // make files virtual QVERIFY(fakeFolder.applyLocalModificationsAndSync()); @@ -1036,7 +1037,7 @@ private Q_SLOTS: if (vfsMode != Vfs::Off) { - fakeFolder.syncJournal().internalPinStates().setForPath("", PinState::AlwaysLocal); + fakeFolder.syncJournal()->internalPinStates().setForPath("", PinState::AlwaysLocal); QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); } diff --git a/test/testutils/syncenginetestutils.cpp b/test/testutils/syncenginetestutils.cpp index 79e6b521996..1d1d0459d2a 100644 --- a/test/testutils/syncenginetestutils.cpp +++ b/test/testutils/syncenginetestutils.cpp @@ -884,27 +884,25 @@ FakeFolder::FakeFolder(const FileInfo &fileTemplate, OCC::Vfs::Mode vfsMode, boo _fakeAm = dynamic_cast(_accountState->account()->accessManager()); Q_ASSERT(_fakeAm); - _journalDb.reset(new OCC::SyncJournalDb(localPath() + QStringLiteral(".sync_test.db"))); + _journalDb = new OCC::SyncJournalDb(localPath() + QStringLiteral(".sync_test.db"), this); // TODO: davUrl - _syncEngine.reset(new OCC::SyncEngine(account(), account()->davUrl(), localPath(), QString(), _journalDb.get())); - _syncEngine->setSyncOptions(OCC::SyncOptions { QSharedPointer(OCC::VfsPluginManager::instance().createVfsFromPlugin(vfsMode).release()) }); + _syncEngine = new OCC::SyncEngine(account(), account()->davUrl(), localPath(), QString(), _journalDb, this); + OCC::Vfs *vfs = OCC::VfsPluginManager::instance().createVfsFromPlugin(vfsMode, this); + Q_ASSERT(vfs && vfs->mode() == vfsMode); + + // make sure the sync engine options are *not* yet set - for the tests this happens in switchToVfs + Q_ASSERT(!_syncEngine->syncOptions().isValid()); // Ignore temporary files from the download. (This is in the default exclude list, but we don't load it) _syncEngine->addManualExclude(QStringLiteral("]*.~*")); - auto vfs = _syncEngine->syncOptions()._vfs; - if (vfsMode != vfs->mode()) { - vfs.reset(OCC::VfsPluginManager::instance().createVfsFromPlugin(vfsMode).release()); - Q_ASSERT(vfs); - } - - // Ensure we have a valid Vfs instance "running" + // start the vfs instance switchToVfs(vfs); if (vfsMode != OCC::Vfs::Off) { const auto pinState = filesAreDehydrated ? OCC::PinState::OnlineOnly : OCC::PinState::AlwaysLocal; - syncJournal().internalPinStates().setForPath("", pinState); + syncJournal()->internalPinStates().setForPath("", pinState); OC_ENFORCE(vfs->setPinState(QString(), pinState)); } @@ -914,38 +912,59 @@ FakeFolder::FakeFolder(const FileInfo &fileTemplate, OCC::Vfs::Mode vfsMode, boo OC_ENFORCE(syncOnce()) } -FakeFolder::~FakeFolder() { } +FakeFolder::~FakeFolder() +{ + if (_syncEngine && _syncEngine->syncOptions().isValid()) { + _syncEngine->syncOptions().vfs()->stop(); + _syncEngine->syncOptions().vfs()->unregisterFolder(); + } +} -void FakeFolder::switchToVfs(QSharedPointer vfs) +void FakeFolder::switchToVfs(OCC::Vfs *vfs) { + Q_ASSERT(vfs); + qDebug() << "switching vfs to " << vfs->mode(); + auto opts = _syncEngine->syncOptions(); - opts._vfs->stop(); - opts._vfs->unregisterFolder(); - QObject::disconnect(_syncEngine.get(), nullptr, opts._vfs.data(), nullptr); + if (opts.isValid()) { + qDebug() << "last vfs was valid with mode: " << opts.vfs()->mode(); - opts._vfs = vfs; - _syncEngine->setSyncOptions(opts); + // we might get strange problems with "dead" vfs instances that have not been stopped because the FakeFolder switchToVfs is missing + // *a lot* of extra steps that normal Folder implements when switching the mode + if (_syncEngine->isSyncRunning()) { + qDebug() << "sync was still running when switch vfs called, exec until done"; + execUntilFinished(); + } + qDebug() << "killing old vfs"; + OCC::Vfs *vfsToDie = opts.vfs(); + vfsToDie->stop(); + vfsToDie->unregisterFolder(); + QObject::disconnect(_syncEngine, nullptr, vfsToDie, nullptr); + QObject::disconnect(_syncEngine->syncFileStatusTracker(), nullptr, vfsToDie, nullptr); + QObject::disconnect(vfsToDie); + // this is "nice" to avoid waiting for the parent folder to die + vfsToDie->deleteLater(); + } + + // todo: nooooooo - the opts should be treated as immutable. that change is coming so this will have to end sometime, starting now. + // opts._vfs = vfs; + qDebug() << "setting new sync options on engine with new vfs"; + _syncEngine->setSyncOptions(OCC::SyncOptions{vfs}); - OCC::VfsSetupParams vfsParams(account(), account()->davUrl(), &syncEngine()); + OCC::VfsSetupParams vfsParams(account(), account()->davUrl(), syncEngine()); vfsParams.filesystemPath = localPath(); vfsParams.remotePath = QLatin1Char('/'); - vfsParams.journal = _journalDb.get(); + vfsParams.journal = _journalDb; vfsParams.providerName = QStringLiteral("OC-TEST"); vfsParams.providerDisplayName = QStringLiteral("OC-TEST"); vfsParams.providerVersion = QVersionNumber(0, 1, 0); vfsParams.multipleAccountsRegistered = false; - QObject::connect(_syncEngine.get(), &QObject::destroyed, this, [vfs]() { - vfs->stop(); - vfs->unregisterFolder(); - }); - QObject::connect(&_syncEngine->syncFileStatusTracker(), &OCC::SyncFileStatusTracker::fileStatusChanged, - vfs.data(), &OCC::Vfs::fileStatusChanged); - QObject::connect(vfs.get(), &OCC::Vfs::error, vfs.get(), [](const QString &error) { - QFAIL(qUtf8Printable(error)); - }); - QSignalSpy spy(vfs.get(), &OCC::Vfs::started); + QObject::connect(_syncEngine->syncFileStatusTracker(), &OCC::SyncFileStatusTracker::fileStatusChanged, vfs, &OCC::Vfs::fileStatusChanged); + + QObject::connect(vfs, &OCC::Vfs::error, vfs, [](const QString &error) { QFAIL(qUtf8Printable(error)); }); + QSignalSpy spy(vfs, &OCC::Vfs::started); vfs->start(vfsParams); // don't use QVERIFY outside of the test slot @@ -974,18 +993,18 @@ QString FakeFolder::localPath() const void FakeFolder::scheduleSync() { // Have to be done async, else, an error before exec() does not terminate the event loop. - QMetaObject::invokeMethod(_syncEngine.get(), &OCC::SyncEngine::startSync, Qt::QueuedConnection); + QMetaObject::invokeMethod(_syncEngine, &OCC::SyncEngine::startSync, Qt::QueuedConnection); } void FakeFolder::execUntilBeforePropagation() { - QSignalSpy spy(_syncEngine.get(), &OCC::SyncEngine::aboutToPropagate); + QSignalSpy spy(_syncEngine, &OCC::SyncEngine::aboutToPropagate); QVERIFY(spy.wait()); } void FakeFolder::execUntilItemCompleted(const QString &relativePath) { - QSignalSpy spy(_syncEngine.get(), &OCC::SyncEngine::itemCompleted); + QSignalSpy spy(_syncEngine, &OCC::SyncEngine::itemCompleted); QElapsedTimer t; t.start(); while (t.elapsed() < 5000) { @@ -1005,9 +1024,9 @@ bool FakeFolder::isDehydratedPlaceholder(const QString &filePath) return vfs()->isDehydratedPlaceholder(filePath); } -QSharedPointer FakeFolder::vfs() const +OCC::Vfs *FakeFolder::vfs() const { - return _syncEngine->syncOptions()._vfs; + return _syncEngine->syncOptions().vfs(); } void FakeFolder::toDisk(QDir &dir, const FileInfo &templateFi) @@ -1116,7 +1135,7 @@ FileInfo FakeFolder::dbState() const bool FakeFolder::execUntilFinished() { - QSignalSpy spy(_syncEngine.get(), &OCC::SyncEngine::finished); + QSignalSpy spy(_syncEngine, &OCC::SyncEngine::finished); bool ok = spy.wait(3600000); Q_ASSERT(ok && "Sync timed out"); return spy[0][0].toBool(); @@ -1126,11 +1145,10 @@ bool FakeFolder::syncOnce() { QObject connectScope; QList> errors; - connect(_syncEngine.get(), &OCC::SyncEngine::syncError, &connectScope, + connect(_syncEngine, &OCC::SyncEngine::syncError, &connectScope, [&errors](const QString &message, OCC::ErrorCategory category) { errors << qMakePair(message, category); }); OCC::SyncResult result; - connect( - _syncEngine.get(), &OCC::SyncEngine::itemCompleted, &connectScope, [&result](const OCC::SyncFileItemPtr &item) { result.processCompletedItem(item); }); + connect(_syncEngine, &OCC::SyncEngine::itemCompleted, &connectScope, [&result](const OCC::SyncFileItemPtr &item) { result.processCompletedItem(item); }); scheduleSync(); const bool ok = execUntilFinished(); if (!ok) { diff --git a/test/testutils/syncenginetestutils.h b/test/testutils/syncenginetestutils.h index 7f144f1deed..cc08738fc13 100644 --- a/test/testutils/syncenginetestutils.h +++ b/test/testutils/syncenginetestutils.h @@ -562,23 +562,24 @@ class FakeCredentials : public OCC::AbstractCredentials class FakeFolder : public QObject { Q_OBJECT - FakeAM *_fakeAm; + // oof, this has a default ctr but I don't see it ever instantiated? + FakeAM *_fakeAm = nullptr; const QTemporaryDir _tempDir = OCC::TestUtils::createTempDir(); DiskFileModifier _localModifier; OCC::TestUtils::TestUtilsPrivate::AccountStateRaii _accountState = OCC::TestUtils::TestUtilsPrivate::AccountStateRaii{nullptr, &OCC::TestUtils::TestUtilsPrivate::accountStateDeleter}; - std::unique_ptr _journalDb; - std::unique_ptr _syncEngine; + OCC::SyncJournalDb *_journalDb = nullptr; + OCC::SyncEngine *_syncEngine = nullptr; public: FakeFolder(const FileInfo &fileTemplate, OCC::Vfs::Mode vfsMode = OCC::Vfs::Off, bool filesAreDehydrated = false); ~FakeFolder(); - void switchToVfs(QSharedPointer vfs); + void switchToVfs(OCC::Vfs *vfs); OCC::Account *account() const { return _accountState->account(); } - OCC::SyncEngine &syncEngine() const { return *_syncEngine; } - OCC::SyncJournalDb &syncJournal() const { return *_journalDb; } + OCC::SyncEngine *syncEngine() const { return _syncEngine; } + OCC::SyncJournalDb *syncJournal() const { return _journalDb; } FileModifier &localModifier() { return _localModifier; } FileInfo &remoteModifier() { return _fakeAm->currentRemoteState(); } @@ -626,7 +627,7 @@ class FakeFolder : public QObject } bool isDehydratedPlaceholder(const QString &filePath); - QSharedPointer vfs() const; + OCC::Vfs *vfs() const; private: static void toDisk(QDir &dir, const FileInfo &templateFi); @@ -654,7 +655,7 @@ inline const FileInfo *findConflict(FileInfo &dir, const QString &filename) struct ItemCompletedSpy : QSignalSpy { explicit ItemCompletedSpy(FakeFolder &folder) - : QSignalSpy(&folder.syncEngine(), &OCC::SyncEngine::itemCompleted) + : QSignalSpy(folder.syncEngine(), &OCC::SyncEngine::itemCompleted) { } diff --git a/test/testwinvfs.cpp b/test/testwinvfs.cpp index bff7917c015..ce0ed3340ef 100755 --- a/test/testwinvfs.cpp +++ b/test/testwinvfs.cpp @@ -41,20 +41,20 @@ bool itemInstruction(const QSignalSpy &spy, const QString &path, const SyncInstr SyncJournalFileRecord dbRecord(FakeFolder &folder, const QString &path) { SyncJournalFileRecord record; - folder.syncJournal().getFileRecord(path, &record); + folder.syncJournal()->getFileRecord(path, &record); return record; } void markForDownload(FakeFolder &folder, const QByteArray &path) { - auto &journal = folder.syncJournal(); + auto journal = folder.syncJournal(); SyncJournalFileRecord record; - journal.getFileRecord(path, &record); + journal->getFileRecord(path, &record); if (!record.isValid()) return; record._type = ItemTypeVirtualFileDownload; - journal.setFileRecord(record); - journal.schedulePathForRemoteDiscovery(record._path); + journal->setFileRecord(record); + journal->schedulePathForRemoteDiscovery(record._path); } bool isPlaceholder(const QString &path) @@ -123,7 +123,7 @@ private Q_SLOTS: FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; fakeFolder.account()->setCapabilities({fakeFolder.account()->url(), TestUtils::testCapabilities(CheckSums::Algorithm::SHA1)}); - QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); auto cleanup = [&]() { completeSpy.clear(); }; @@ -175,7 +175,7 @@ private Q_SLOTS: QCOMPARE(QFileInfo(localA1).size(), 5_MiB); QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a1"))._type, ItemTypeFile); QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a1"))._checksumHeader, "SHA1:8886930866e6faf7558cda88305d92d13ee26cc9"); - QCOMPARE(*fakeFolder.vfs()->pinState(QStringLiteral("A/a1")), PinState::Unspecified); // since it was an implicit hydration + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a1")), PinState::Unspecified); // since it was an implicit hydration // // Is the remote etag propagated if it changed? @@ -274,7 +274,7 @@ private Q_SLOTS: { FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; - QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); auto cleanup = [&]() { completeSpy.clear(); }; @@ -306,7 +306,7 @@ private Q_SLOTS: QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - QCOMPARE(*fakeFolder.vfs()->pinState(QStringLiteral("A/a2")), PinState::AlwaysLocal); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a2")), PinState::AlwaysLocal); QCOMPARE(isPlaceholderWithOnDiskSize(localA1), 5_MiB); QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a1"))._type, ItemTypeFile); @@ -315,8 +315,8 @@ private Q_SLOTS: QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/a1"), CSYNC_INSTRUCTION_SYNC)); QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/a2"), CSYNC_INSTRUCTION_SYNC)); - QCOMPARE(*fakeFolder.vfs()->pinState(QStringLiteral("A/a1")), PinState::Unspecified); // since no attribute got propagated - QCOMPARE(*fakeFolder.vfs()->pinState(QStringLiteral("A/a2")), PinState::AlwaysLocal); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a1")), PinState::Unspecified); // since no attribute got propagated + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a2")), PinState::AlwaysLocal); cleanup(); // @@ -326,8 +326,8 @@ private Q_SLOTS: fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a2")); QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(*fakeFolder.vfs()->pinState(QStringLiteral("A/a1")), PinState::Unspecified); // since no attribute got propagated - QCOMPARE(*fakeFolder.vfs()->pinState(QStringLiteral("A/a2")), PinState::AlwaysLocal); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a1")), PinState::Unspecified); // since no attribute got propagated + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a2")), PinState::AlwaysLocal); cleanup(); } @@ -336,7 +336,7 @@ private Q_SLOTS: { FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; - QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); auto cleanup = [&]() { completeSpy.clear(); }; @@ -393,7 +393,7 @@ private Q_SLOTS: { FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; - QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); auto cleanup = [&]() { completeSpy.clear(); }; @@ -438,7 +438,7 @@ private Q_SLOTS: { FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; - QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); auto cleanup = [&]() { completeSpy.clear(); }; @@ -499,7 +499,7 @@ private Q_SLOTS: FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; auto l = [&](const QString &p) { return QString(fakeFolder.localPath() + p); }; - QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); auto cleanup = [&]() { completeSpy.clear(); }; @@ -546,7 +546,7 @@ private Q_SLOTS: QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("case3-rename"))); QVERIFY(itemInstruction(completeSpy, QStringLiteral("case3-rename"), CSYNC_INSTRUCTION_RENAME)); QCOMPARE(dbRecord(fakeFolder, QStringLiteral("case3-rename"))._type, ItemTypeVirtualFile); - QCOMPARE(*fakeFolder.vfs()->pinState(QStringLiteral("case3-rename")), PinState::AlwaysLocal); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("case3-rename")), PinState::AlwaysLocal); // Case 4: the rename went though, pin state change still on file QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("case4"))); @@ -556,7 +556,7 @@ private Q_SLOTS: QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("case4-rename"))); QVERIFY(itemInstruction(completeSpy, QStringLiteral("case4-rename"), CSYNC_INSTRUCTION_RENAME)); QCOMPARE(dbRecord(fakeFolder, QStringLiteral("case4-rename"))._type, ItemTypeFile); - QCOMPARE(*fakeFolder.vfs()->pinState(QStringLiteral("case4-rename")), PinState::OnlineOnly); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("case4-rename")), PinState::OnlineOnly); // at this point it'd be nice if the client had anotherSyncNeeded set, to // make sure the pin state change gets applied! @@ -579,7 +579,7 @@ private Q_SLOTS: QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); auto cleanup = [&]() { completeSpy.clear(); }; @@ -681,6 +681,7 @@ private Q_SLOTS: fakeFolder.localModifier().insert(QStringLiteral("A/a3"), 100_B); QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); + // todo: review this. AFAIK it's already covered in FakeFolder::swtichToVfs or *should* be fakeFolder.vfs()->wipeDehydratedVirtualFiles(); fakeFolder.vfs()->stop(); fakeFolder.vfs()->unregisterFolder(); @@ -691,11 +692,11 @@ private Q_SLOTS: QVERIFY(!QFile::exists(l(QStringLiteral("A/B/b1")))); // Check that syncing with vfs disabled is fine - auto vfsOff = QSharedPointer(VfsPluginManager::instance().createVfsFromPlugin(Vfs::Off).release()); + auto vfsOff = VfsPluginManager::instance().createVfsFromPlugin(Vfs::Off, &fakeFolder); QVERIFY(vfsOff); fakeFolder.switchToVfs(vfsOff); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - auto conflicts = fakeFolder.syncJournal().conflictRecordPaths(); + auto conflicts = fakeFolder.syncJournal()->conflictRecordPaths(); QCOMPARE(conflicts.size(), 1); QFile::remove(l(QString::fromUtf8(conflicts[0]))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -708,9 +709,9 @@ private Q_SLOTS: fakeFolder.remoteModifier().insert(QStringLiteral("A/a1")); fakeFolder.remoteModifier().insert(QStringLiteral("A/a2")); fakeFolder.syncOnce(); - QCOMPARE(*fakeFolder.vfs()->availability(QString()), VfsItemAvailability::AllDehydrated); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A")), VfsItemAvailability::AllDehydrated); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A/a1")), VfsItemAvailability::AllDehydrated); + QCOMPARE(fakeFolder.vfs()->availability(QString()), VfsItemAvailability::AllDehydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A")), VfsItemAvailability::AllDehydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a1")), VfsItemAvailability::AllDehydrated); // The Vfs property is only accessible when it's not "sync pending" auto updateSyncState = [&] { @@ -722,31 +723,31 @@ private Q_SLOTS: QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A"), PinState::AlwaysLocal)); fakeFolder.syncOnce(); updateSyncState(); - QCOMPARE(*fakeFolder.vfs()->availability(QString()), VfsItemAvailability::AllHydrated); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A")), VfsItemAvailability::AlwaysLocal); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A/a1")), VfsItemAvailability::AlwaysLocal); + QCOMPARE(fakeFolder.vfs()->availability(QString()), VfsItemAvailability::AllHydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A")), VfsItemAvailability::AlwaysLocal); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a1")), VfsItemAvailability::AlwaysLocal); QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/a1"), PinState::Unspecified)); fakeFolder.syncOnce(); updateSyncState(); - QCOMPARE(*fakeFolder.vfs()->availability(QString()), VfsItemAvailability::AllHydrated); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A")), VfsItemAvailability::AllHydrated); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A/a1")), VfsItemAvailability::AllHydrated); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A/a2")), VfsItemAvailability::AlwaysLocal); + QCOMPARE(fakeFolder.vfs()->availability(QString()), VfsItemAvailability::AllHydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A")), VfsItemAvailability::AllHydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a1")), VfsItemAvailability::AllHydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a2")), VfsItemAvailability::AlwaysLocal); QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A"), PinState::OnlineOnly)); fakeFolder.syncOnce(); updateSyncState(); - QCOMPARE(*fakeFolder.vfs()->availability(QString()), VfsItemAvailability::AllDehydrated); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A")), VfsItemAvailability::AllDehydrated); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A/a1")), VfsItemAvailability::AllDehydrated); + QCOMPARE(fakeFolder.vfs()->availability(QString()), VfsItemAvailability::AllDehydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A")), VfsItemAvailability::AllDehydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a1")), VfsItemAvailability::AllDehydrated); QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/a1"), PinState::AlwaysLocal)); fakeFolder.syncOnce(); updateSyncState(); - QCOMPARE(*fakeFolder.vfs()->availability(QString()), VfsItemAvailability::Mixed); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A")), VfsItemAvailability::Mixed); - QCOMPARE(*fakeFolder.vfs()->availability(QStringLiteral("A/a1")), VfsItemAvailability::AlwaysLocal); + QCOMPARE(fakeFolder.vfs()->availability(QString()), VfsItemAvailability::Mixed); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A")), VfsItemAvailability::Mixed); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a1")), VfsItemAvailability::AlwaysLocal); } // Check that previously hydrated files become placeholders on sync @@ -757,8 +758,8 @@ private Q_SLOTS: fakeFolder.remoteModifier().insert(QStringLiteral("A/a1"), 64_B); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - fakeFolder.switchToVfs(QSharedPointer(new VfsWin)); - QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + fakeFolder.switchToVfs(new VfsWin(&fakeFolder)); + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/a1"), CSYNC_INSTRUCTION_UPDATE_METADATA));