project(client)
+if(UNIT_TESTING)
+ include(CTest)
+ enable_testing()
+endif()
+
set(BIN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
include(${CMAKE_SOURCE_DIR}/VERSION.cmake)
# For config.h
include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR})
+#include_directories(BEFORE
+# "C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/um"
+# "C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/shared")
# Allows includes based on src/ like #include "common/utility.h" or #include "csync/csync.h"
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src
add_definitions( -D__USE_MINGW_ANSI_STDIO=1 )
add_definitions( -DNOMINMAX )
# Get APIs from from Vista onwards.
-add_definitions( -D_WIN32_WINNT=0x0601 )
-add_definitions( -DWINVER=0x0601 )
+add_definitions(-D_WIN32_WINNT=0x0601)
+add_definitions(-DWINVER=0x0601)
+add_definitions(-DNTDDI_VERSION=0x0A000004)
if( MSVC )
# Use automatic overload for suitable CRT safe-functions
# See https://docs.microsoft.com/de-de/cpp/c-runtime-library/security-features-in-the-crt?view=vs-2019
endif()
if(UNIT_TESTING)
- include(CTest)
- enable_testing()
add_subdirectory(test)
endif(UNIT_TESTING)
if (SUCCEEDED(hr))
{
+ DWORD cbData;
+ const BYTE * lpData;
+
if (pszData)
{
// Set the specified value of the key.
- DWORD cbData = lstrlen(pszData) * sizeof(*pszData);
- hr = HRESULT_FROM_WIN32(RegSetValueEx(hKey, pszValueName, 0,
- REG_SZ, reinterpret_cast<const BYTE *>(pszData), cbData));
+ cbData = lstrlen(pszData) * sizeof(*pszData);
+ lpData = reinterpret_cast<const BYTE *>(pszData);
+ }
+ else
+ {
+ cbData = 0;
+ lpData = NULL;
}
+ hr = HRESULT_FROM_WIN32(RegSetValueEx(hKey, pszValueName, 0,
+ REG_SZ, lpData, cbData));
+
RegCloseKey(hKey);
}
{
hr = SetHKCRRegistryKeyAndValue(szSubkey, nullptr, pszFriendlyName);
+ // Create the HKCR\CLSID\{<CLSID>}\ContextMenuOptIn subkey.
+ if (SUCCEEDED(hr)) {
+ hr = SetHKCRRegistryKeyAndValue(szSubkey, L"ContextMenuOptIn", NULL);
+ }
+
// Create the HKCR\CLSID\{<CLSID>}\InprocServer32 key.
if (SUCCEEDED(hr))
{
${PROJECT_SOURCE_DIR}/src/cmd/*.cpp
)
endif()
+
${CMAKE_CURRENT_LIST_DIR}/syncjournalfilerecord.cpp
${CMAKE_CURRENT_LIST_DIR}/utility.cpp
${CMAKE_CURRENT_LIST_DIR}/remotepermissions.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/vfs.cpp
)
OCSYNC_EXPORT bool registryDeleteKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName);
OCSYNC_EXPORT bool registryWalkSubKeys(HKEY hRootKey, const QString &subKey, const std::function<void(HKEY, const QString &)> &callback);
OCSYNC_EXPORT QRect getTaskbarDimensions();
+
+ // Possibly refactor to share code with UnixTimevalToFileTime in c_time.c
+ OCSYNC_EXPORT void UnixTimeToFiletime(time_t t, FILETIME *filetime);
+ OCSYNC_EXPORT void FiletimeToLargeIntegerFiletime(FILETIME *filetime, LARGE_INTEGER *hundredNSecs);
+ OCSYNC_EXPORT void UnixTimeToLargeIntegerFiletime(time_t t, LARGE_INTEGER *hundredNSecs);
+
#endif
}
/** @} */ // \addtogroup
return static_cast<DWORD>(convertVar);
}
+void Utility::UnixTimeToFiletime(time_t t, FILETIME *filetime)
+{
+ LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000;
+ filetime->dwLowDateTime = (DWORD) ll;
+ filetime->dwHighDateTime = ll >>32;
+}
+
+void Utility::FiletimeToLargeIntegerFiletime(FILETIME *filetime, LARGE_INTEGER *hundredNSecs)
+{
+ hundredNSecs->LowPart = filetime->dwLowDateTime;
+ hundredNSecs->HighPart = filetime->dwHighDateTime;
+}
+
+void Utility::UnixTimeToLargeIntegerFiletime(time_t t, LARGE_INTEGER *hundredNSecs)
+{
+ LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000;
+ hundredNSecs->LowPart = (DWORD) ll;
+ hundredNSecs->HighPart = ll >>32;
+}
+
} // namespace OCC
--- /dev/null
+/*
+ * Copyright (C) by Dominik Schmidt <dschmidt@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "vfs.h"
+
+using namespace OCC;
+
+Vfs::Vfs(QObject* parent)
+ : QObject(parent)
+{
+}
+
+Vfs::~Vfs() = default;
+
+QString Vfs::modeToString(Mode mode)
+{
+ // Note: Strings are used for config and must be stable
+ switch (mode) {
+ case Off:
+ return QStringLiteral("off");
+ case WithSuffix:
+ return QStringLiteral("suffix");
+ case WindowsCfApi:
+ return QStringLiteral("wincfapi");
+ }
+ return QStringLiteral("off");
+}
+
+bool Vfs::modeFromString(const QString &str, Mode *mode)
+{
+ // Note: Strings are used for config and must be stable
+ *mode = Off;
+ if (str == "off") {
+ return true;
+ } else if (str == "suffix") {
+ *mode = WithSuffix;
+ return true;
+ } else if (str == "wincfapi") {
+ *mode = WindowsCfApi;
+ return true;
+ }
+ return false;
+}
--- /dev/null
+/*
+ * Copyright (C) by Christian Kamm <mail@ckamm.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#pragma once
+
+#include <QObject>
+#include <QScopedPointer>
+#include <QSharedPointer>
+
+#include "ocsynclib.h"
+
+typedef struct csync_file_stat_s csync_file_stat_t;
+
+namespace OCC {
+
+class Account;
+typedef QSharedPointer<Account> AccountPtr;
+class SyncJournalDb;
+class VfsPrivate;
+class SyncFileItem;
+typedef QSharedPointer<SyncFileItem> SyncFileItemPtr;
+
+struct OCSYNC_EXPORT VfsSetupParams
+{
+ QString filesystemPath;
+ QString remotePath;
+
+ AccountPtr account;
+ // The journal must live at least until the stop() call
+ SyncJournalDb *journal;
+
+ QString providerName;
+ QString providerVersion;
+};
+
+class OCSYNC_EXPORT Vfs : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum Mode
+ {
+ Off,
+ WithSuffix,
+ WindowsCfApi,
+ };
+ static QString modeToString(Mode mode);
+ static bool modeFromString(const QString &str, Mode *mode);
+
+public:
+ Vfs(QObject* parent = nullptr);
+ virtual ~Vfs();
+
+ virtual Mode mode() const = 0;
+
+ // For WithSuffix modes: what's the suffix (including the dot)?
+ virtual QString fileSuffix() const = 0;
+
+ virtual void registerFolder(const VfsSetupParams ¶ms) = 0;
+ virtual void start(const VfsSetupParams ¶ms) = 0;
+ virtual void stop() = 0;
+ virtual void unregisterFolder() = 0;
+
+ virtual bool isHydrating() const = 0;
+
+ // Update placeholder metadata during discovery
+ virtual bool updateMetadata(const QString &filePath, time_t modtime, quint64 size, const QByteArray &fileId, QString *error) = 0;
+
+ // Create and convert placeholders in PropagateDownload
+ virtual void createPlaceholder(const QString &syncFolder, const SyncFileItemPtr &item) = 0;
+ virtual void convertToPlaceholder(const QString &filename, const SyncFileItemPtr &item) = 0;
+
+ // Determine whether something is a placeholder
+ virtual bool isDehydratedPlaceholder(const QString &filePath) = 0;
+
+ // Determine whether something is a placeholder in discovery
+ // stat has at least 'path' filled
+ // the stat_data argument has platform specific data
+ // returning true means that the file_stat->type was set and should be fixed
+ virtual bool statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) = 0;
+
+signals:
+ void beginHydrating();
+ void doneHydrating();
+};
+
+} // namespace OCC
ItemTypeDirectory = 2,
ItemTypeSkip = 3,
ItemTypeVirtualFile = 4,
- ItemTypeVirtualFileDownload = 5
+ ItemTypeVirtualFileDownload = 5,
+ ItemTypeVirtualFileDehydration = 6,
};
// currently specified at https://github.com/owncloud/core/issues/8322 are 9 to 10
#define REMOTE_PERM_BUF_SIZE 15
-struct OCSYNC_EXPORT csync_file_stat_t {
+typedef struct csync_file_stat_s csync_file_stat_t;
+
+struct OCSYNC_EXPORT csync_file_stat_s {
time_t modtime = 0;
int64_t size = 0;
uint64_t inode = 0;
enum csync_instructions_e instruction = CSYNC_INSTRUCTION_NONE; /* u32 */
- csync_file_stat_t()
+ csync_file_stat_s()
: type(ItemTypeSkip)
, child_modified(false)
, has_ignored_files(false)
csync_vio_handle_t OCSYNC_EXPORT *csync_vio_local_opendir(const QString &name);
int OCSYNC_EXPORT csync_vio_local_closedir(csync_vio_handle_t *dhandle);
-std::unique_ptr<csync_file_stat_t> OCSYNC_EXPORT csync_vio_local_readdir(csync_vio_handle_t *dhandle);
+std::unique_ptr<csync_file_stat_t> OCSYNC_EXPORT csync_vio_local_readdir(CSYNC *ctx, csync_vio_handle_t *dhandle);
int OCSYNC_EXPORT csync_vio_local_stat(const char *uri, csync_file_stat_t *buf);
#include "csync_util.h"
#include "vio/csync_vio_local.h"
+#include "common/vfsplugin.h"
#include <QtCore/QLoggingCategory>
#include <QtCore/QFile>
return rc;
}
-std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(csync_vio_handle_t *handle) {
+std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(CSYNC *ctx, csync_vio_handle_t *handle) {
struct _tdirent *dirent = NULL;
std::unique_ptr<csync_file_stat_t> file_stat;
// Will get excluded by _csync_detect_update.
file_stat->type = ItemTypeSkip;
}
+
+ // Override type for virtual files if desired
+ if (ctx->vfs)
+ ctx->vfs->statTypeVirtualFile(file_stat.get(), nullptr);
+
return file_stat;
}
#include <QtCore/QLoggingCategory>
+#include "common/vfs.h"
+
Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "nextcloud.sync.csync.vio_local", QtInfoMsg)
/*
}
}
-std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(csync_vio_handle_t *handle) {
+std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(CSYNC *ctx, csync_vio_handle_t *handle) {
std::unique_ptr<csync_file_stat_t> file_stat;
DWORD rem;
}
auto path = c_utf8_from_locale(handle->ffd.cFileName);
if (path == "." || path == "..")
- return csync_vio_local_readdir(handle);
+ return csync_vio_local_readdir(ctx, handle);
file_stat = std::make_unique<csync_file_stat_t>();
file_stat->path = path;
- if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ if (ctx->vfs && ctx->vfs->statTypeVirtualFile(file_stat.get(), &handle->ffd)) {
+ // all good
+ } else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
// Detect symlinks, and treat junctions as symlinks too.
if (handle->ffd.dwReserved0 == IO_REPARSE_TAG_SYMLINK
|| handle->ffd.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT) {
folderWizard->field(QLatin1String("sourceFolder")).toString());
definition.targetPath = FolderDefinition::prepareTargetPath(
folderWizard->property("targetPath").toString());
- definition.useVirtualFiles = folderWizard->property("useVirtualFiles").toBool();
+
+ if (folderWizard->property("useVirtualFiles").toBool()) {
+ // ### Determine which vfs is available?
+ definition.virtualFilesMode = Vfs::WindowsCfApi;
+ }
{
QDir dir(definition.localPath);
#include "filesystem.h"
#include "localdiscoverytracker.h"
#include "csync_exclude.h"
-
+#include "common/vfs.h"
+#include "plugin.h"
#include "creds/abstractcredentials.h"
#include <QTimer>
#include <QMessageBox>
#include <QPushButton>
+#include <QApplication>
static const char versionC[] = "version";
_localDiscoveryTracker.data(), &LocalDiscoveryTracker::slotSyncFinished);
connect(_engine.data(), &SyncEngine::itemCompleted,
_localDiscoveryTracker.data(), &LocalDiscoveryTracker::slotItemCompleted);
+
+ // TODO cfapi: Move to function. Is this platform-universal?
+ PluginLoader pluginLoader;
+ if (_definition.virtualFilesMode == Vfs::WindowsCfApi) {
+ _vfs = pluginLoader.create<Vfs>("vfs", "win", this);
+ }
+ if (_definition.virtualFilesMode == Vfs::WithSuffix) {
+ _vfs = pluginLoader.create<Vfs>("vfs", "suffix", this);
+
+ // Attempt to switch to winvfs mode?
+ if (_vfs && _definition.upgradeVfsMode) {
+ if (auto winvfs = pluginLoader.create<Vfs>("vfs", "win", this)) {
+ // Set "suffix" vfs options and wipe the existing suffix files
+ SyncEngine::wipeVirtualFiles(path(), _journal, _vfs);
+
+ // Then switch to winvfs mode
+ _vfs = winvfs;
+ _definition.virtualFilesMode = Vfs::WindowsCfApi;
+ saveToSettings();
+ }
+ }
+ }
+ if (!_vfs) {
+ // ### error handling; possibly earlier than in the ctor
+ qFatal("Could not load any vfs plugin.");
+ }
+
+ VfsSetupParams vfsParams;
+ vfsParams.filesystemPath = path();
+ vfsParams.remotePath = remotePath();
+ vfsParams.account = _accountState->account();
+ vfsParams.journal = &_journal;
+ vfsParams.providerName = Theme::instance()->appNameGUI();
+ vfsParams.providerVersion = Theme::instance()->version();
+
+ connect(_vfs, &OCC::Vfs::beginHydrating, this, &Folder::slotHydrationStarts);
+ connect(_vfs, &OCC::Vfs::doneHydrating, this, &Folder::slotHydrationDone);
+
+ _vfs->registerFolder(vfsParams); // Do this always?
+ _vfs->start(vfsParams);
}
Folder::~Folder()
{
+ // TODO cfapi: unregister on wipe()? There should probably be a wipeForRemoval() where this cleanup is appropriate
+ _vfs->stop();
+
// Reset then engine first as it will abort and try to access members of the Folder
_engine.reset();
}
bool Folder::isBusy() const
{
- return _engine->isSyncRunning();
+ return isSyncRunning();
}
bool Folder::isSyncRunning() const
{
- return _engine->isSyncRunning();
+ return _engine->isSyncRunning() || _vfs->isHydrating();
}
QString Folder::remotePath() const
void Folder::setUseVirtualFiles(bool enabled)
{
- _definition.useVirtualFiles = enabled;
+ // ### must wipe virtual files, unload old plugin, load new one?
+ //_definition.useVirtualFiles = enabled;
if (enabled)
_saveInFoldersWithPlaceholders = true;
saveToSettings();
return other != this && other->cleanPath() == this->cleanPath();
});
- if (_definition.useVirtualFiles || _saveInFoldersWithPlaceholders) {
+ if (useVirtualFiles() || _saveInFoldersWithPlaceholders) {
// If virtual files are enabled or even were enabled at some point,
// save the folder to a group that will not be read by older (<2.5.0) clients.
// The name is from when virtual files were called placeholders.
opt._newBigFolderSizeLimit = newFolderLimit.first ? newFolderLimit.second * 1000LL * 1000LL : -1; // convert from MB to B
opt._confirmExternalStorage = cfgFile.confirmExternalStorage();
opt._moveFilesToTrash = cfgFile.moveToTrash();
- opt._newFilesAreVirtual = _definition.useVirtualFiles;
- opt._virtualFileSuffix = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
+ opt._vfs = _vfs;
QByteArray chunkSizeEnv = qgetenv("OWNCLOUD_CHUNK_SIZE");
if (!chunkSizeEnv.isEmpty()) {
Logger::instance()->postGuiLog(Theme::instance()->appNameGUI(), fullMessage);
}
+void Folder::slotHydrationStarts()
+{
+ // Abort any running full sync run and reschedule
+ if (_engine->isSyncRunning()) {
+ slotTerminateSync();
+ scheduleThisFolderSoon();
+ // TODO: This sets the sync state to AbortRequested on done, we don't want that
+ }
+
+ // Let everyone know we're syncing
+ _syncResult.reset();
+ _syncResult.setStatus(SyncResult::SyncRunning);
+ emit syncStarted();
+ emit syncStateChange();
+}
+
+void Folder::slotHydrationDone()
+{
+ // emit signal to update ui and reschedule normal syncs if necessary
+ _syncResult.setStatus(SyncResult::Success);
+ emit syncFinished(_syncResult);
+ emit syncStateChange();
+}
+
void Folder::scheduleThisFolderSoon()
{
if (!_scheduleSelfTimer.isActive()) {
_folderWatcher->startNotificatonTest(path() + QLatin1String(".owncloudsync.log"));
}
+bool Folder::useVirtualFiles() const
+{
+ return _definition.virtualFilesMode != Vfs::Off;
+}
+
void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction dir, bool *cancel)
{
ConfigFile cfgFile;
settings.setValue(QLatin1String("targetPath"), folder.targetPath);
settings.setValue(QLatin1String("paused"), folder.paused);
settings.setValue(QLatin1String("ignoreHiddenFiles"), folder.ignoreHiddenFiles);
- settings.setValue(QLatin1String("usePlaceholders"), folder.useVirtualFiles);
settings.setValue(QLatin1String(versionC), maxSettingsVersion());
+ settings.setValue(QStringLiteral("virtualFilesMode"), Vfs::modeToString(folder.virtualFilesMode));
+ settings.remove(QLatin1String("usePlaceholders")); // deprecated key
+
// Happens only on Windows when the explorer integration is enabled.
if (!folder.navigationPaneClsid.isNull())
settings.setValue(QLatin1String("navigationPaneClsid"), folder.navigationPaneClsid);
folder->paused = settings.value(QLatin1String("paused")).toBool();
folder->ignoreHiddenFiles = settings.value(QLatin1String("ignoreHiddenFiles"), QVariant(true)).toBool();
folder->navigationPaneClsid = settings.value(QLatin1String("navigationPaneClsid")).toUuid();
- folder->useVirtualFiles = settings.value(QLatin1String("usePlaceholders")).toBool();
+
+ folder->virtualFilesMode = Vfs::Off;
+ QString vfsModeString = settings.value(QStringLiteral("virtualFilesMode")).toString();
+ if (!vfsModeString.isEmpty()) {
+ if (!Vfs::modeFromString(vfsModeString, &folder->virtualFilesMode)) {
+ qCWarning(lcFolder) << "Unknown virtualFilesMode:" << vfsModeString << "assuming 'off'";
+ }
+ } else if (settings.value(QLatin1String("usePlaceholders")).toBool()) {
+ folder->virtualFilesMode = Vfs::WithSuffix;
+ folder->upgradeVfsMode = true;
+ }
+
settings.endGroup();
// Old settings can contain paths with native separators. In the rest of the
#include "progressdispatcher.h"
#include "common/syncjournaldb.h"
#include "networkjobs.h"
+#include "syncoptions.h"
#include <QObject>
#include <QStringList>
namespace OCC {
+class Vfs;
class SyncEngine;
class AccountState;
class SyncRunFileLog;
bool paused = false;
/// whether the folder syncs hidden files
bool ignoreHiddenFiles = false;
- /// New files are downloaded as virtual files
- bool useVirtualFiles = false;
+ /// Which virtual files setting the folder uses
+ Vfs::Mode virtualFilesMode = Vfs::Off;
/// The CLSID where this folder appears in registry for the Explorer navigation pane entry.
QUuid navigationPaneClsid;
+ /// Whether this suffix-vfs should be migrated to a better
+ /// vfs plugin if possible
+ bool upgradeVfsMode = false;
+
/// Saves the folder definition, creating a new settings group.
static void save(QSettings &settings, const FolderDefinition &folder);
SyncResult syncResult() const;
/**
- * This is called if the sync folder definition is removed. Do cleanups here.
+ * This is called when the sync folder definition is removed. Do cleanups here.
*/
virtual void wipe();
*/
void registerFolderWatcher();
- /** new files are downloaded as virtual files */
- bool useVirtualFiles() { return _definition.useVirtualFiles; }
+ /** virtual files of some kind are enabled */
+ bool useVirtualFiles() const;
void setUseVirtualFiles(bool enabled);
signals:
/** Warn users about an unreliable folder watcher */
void slotWatcherUnreliable(const QString &message);
+ /** Aborts any running sync and blocks it until hydration is finished.
+ *
+ * Hydration circumvents the regular SyncEngine and both mustn't be running
+ * at the same time.
+ */
+ void slotHydrationStarts();
+
+ /** Unblocks normal sync operation */
+ void slotHydrationDone();
+
private:
+ void connectSyncRoot();
+
bool reloadExcludes();
void showSyncResultPopup();
* Keeps track of locally dirty files so we can skip local discovery sometimes.
*/
QScopedPointer<LocalDiscoveryTracker> _localDiscoveryTracker;
+
+ /**
+ * The vfs mode instance (created by plugin) to use. Null means no vfs.
+ */
+ Vfs *_vfs = nullptr;
};
}
#ifdef Q_OS_MAC
#include <CoreServices/CoreServices.h>
#endif
-#ifdef Q_OS_WIN
-#include <shlobj.h>
-#endif
#include <QMessageBox>
#include <QtCore>
}
_navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
-
return folder;
}
folderDefinition.localPath = localFolder;
folderDefinition.targetPath = FolderDefinition::prepareTargetPath(_remoteFolder);
folderDefinition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles();
- folderDefinition.useVirtualFiles = _ocWizard->useVirtualFileSync();
+ if (_ocWizard->useVirtualFileSync()) {
+ // ### determine best vfs mode!
+ folderDefinition.virtualFilesMode = Vfs::WindowsCfApi;
+ }
if (folderMan->navigationPaneHelper().showInExplorerNavigationPane())
folderDefinition.navigationPaneClsid = QUuid::createUuid();
void SocketApi::command_DOWNLOAD_VIRTUAL_FILE(const QString &filesArg, SocketListener *)
{
QStringList files = filesArg.split(QLatin1Char('\x1e')); // Record Separator
- auto suffix = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
for (const auto &file : files) {
- if (!file.endsWith(suffix) && !QFileInfo(file).isDir())
+ auto data = FileData::get(file);
+ auto record = data.journalRecord();
+ if (!record.isValid())
continue;
- auto folder = FolderMan::instance()->folderForPath(file);
- if (folder) {
- QString relativePath = QDir::cleanPath(file).mid(folder->cleanPath().length() + 1);
- folder->downloadVirtualFile(relativePath);
- }
+ if (record._type != ItemTypeVirtualFile && !QFileInfo(file).isDir())
+ continue;
+ if (data.folder)
+ data.folder->downloadVirtualFile(data.folderRelativePath);
}
}
-/* Go over all the files ans replace them by a virtual file */
+/* Go over all the files and replace them by a virtual file */
void SocketApi::command_REPLACE_VIRTUAL_FILE(const QString &filesArg, SocketListener *)
{
QStringList files = filesArg.split(QLatin1Char('\x1e')); // Record Separator
- auto suffix = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
+ QSet<Folder *> toSync;
for (const auto &file : files) {
- auto folder = FolderMan::instance()->folderForPath(file);
- if (!folder)
- continue;
- if (file.endsWith(suffix))
+ auto data = FileData::get(file);
+ if (!data.folder)
continue;
- QString relativePath = QDir::cleanPath(file).mid(folder->cleanPath().length() + 1);
+ auto journal = data.folder->journalDb();
+ auto markForDehydration = [&](SyncJournalFileRecord rec) {
+ if (rec._type != ItemTypeFile)
+ return;
+ rec._type = ItemTypeVirtualFileDehydration;
+ journal->setFileRecord(rec);
+ toSync.insert(data.folder);
+ };
+
QFileInfo fi(file);
if (fi.isDir()) {
- folder->journalDb()->getFilesBelowPath(relativePath.toUtf8(), [&](const SyncJournalFileRecord &rec) {
- if (rec._type != ItemTypeFile || rec._path.endsWith(APPLICATION_DOTVIRTUALFILE_SUFFIX))
- return;
- QString file = folder->path() + '/' + QString::fromUtf8(rec._path);
- if (!FileSystem::rename(file, file + suffix)) {
- qCWarning(lcSocketApi) << "Unable to rename " << file;
- }
- });
+ journal->getFilesBelowPath(data.folderRelativePath.toUtf8(), markForDehydration);
continue;
}
- SyncJournalFileRecord record;
- if (!folder->journalDb()->getFileRecord(relativePath, &record) || !record.isValid())
+ auto record = data.journalRecord();
+ if (!record.isValid() || record._type != ItemTypeFile)
continue;
- if (!FileSystem::rename(file, file + suffix)) {
- qCWarning(lcSocketApi) << "Unable to rename " << file;
- }
- FolderMan::instance()->scheduleFolder(folder);
+ markForDehydration(record);
}
+
+ for (const auto folder : toSync)
+ FolderMan::instance()->scheduleFolder(folder);
}
void SocketApi::copyUrlToClipboard(const QString &link)
// Virtual file download action
if (syncFolder) {
- auto virtualFileSuffix = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
bool hasVirtualFile = false;
bool hasNormalFiles = false;
bool hasDir = false;
for (const auto &file : files) {
if (QFileInfo(file).isDir()) {
hasDir = true;
- } else if (file.endsWith(virtualFileSuffix)) {
- hasVirtualFile = true;
- } else if (!hasNormalFiles) {
- bool isOnTheServer = FileData::get(file).journalRecord().isValid();
- hasNormalFiles = isOnTheServer;
+ } else if (!hasVirtualFile || !hasNormalFiles) {
+ auto record = FileData::get(file).journalRecord();
+ if (record.isValid()) {
+ hasVirtualFile |= record._type == ItemTypeVirtualFile;
+ hasNormalFiles |= record._type == ItemTypeFile;
+ }
}
}
if (hasVirtualFile || (hasDir && syncFolder->useVirtualFiles()))
networkjobs.cpp
owncloudpropagator.cpp
nextcloudtheme.cpp
+ plugin.cpp
progressdispatcher.cpp
propagatorjobs.cpp
propagatedownload.cpp
endif()
+add_subdirectory(vfs)
void DiscoveryPhase::checkSelectiveSyncNewFolder(const QString &path, RemotePermissions remotePerm,
std::function<void(bool)> callback)
{
- if (_syncOptions._confirmExternalStorage && !_syncOptions._newFilesAreVirtual
+ if (_syncOptions._confirmExternalStorage && !_syncOptions._vfs
&& remotePerm.hasPermission(RemotePermissions::IsMounted)) {
// external storage.
}
auto limit = _syncOptions._newBigFolderSizeLimit;
- if (limit < 0 || _syncOptions._newFilesAreVirtual) {
+ if (limit < 0 || !_syncOptions._vfs) {
// no limit, everything is allowed;
return callback(false);
}
return allRemoved;
}
+bool FileSystem::getInode(const QString &filename, quint64 *inode)
+{
+ csync_file_stat_t fs;
+ if (csync_vio_local_stat(filename.toUtf8().constData(), &fs) == 0) {
+ *inode = fs.inode;
+ return true;
+ }
+ return false;
+}
+
} // namespace OCC
*/
qint64 OWNCLOUDSYNC_EXPORT getSize(const QString &filename);
+ /**
+ * @brief Retrieve a file inode with csync
+ */
+ bool OWNCLOUDSYNC_EXPORT getInode(const QString &filename, quint64 *inode);
+
/**
* @brief Check if \a fileName has changed given previous size and mtime
*
return _localDir + tmp_file_name;
}
-QString OwncloudPropagator::addVirtualFileSuffix(const QString &fileName) const
-{
- return fileName + _syncOptions._virtualFileSuffix;
-}
-
void OwncloudPropagator::scheduleNextJob()
{
QTimer::singleShot(0, this, &OwncloudPropagator::scheduleNextJobImpl);
/* returns the local file path for the given tmp_file_name */
QString getFilePath(const QString &tmp_file_name) const;
- QString addVirtualFileSuffix(const QString &fileName) const;
/** Creates the job for an item.
*/
--- /dev/null
+/*
+ * Copyright (C) by Dominik Schmidt <dschmidt@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "plugin.h"
+
+#include "config.h"
+#include "logger.h"
+
+#include <QPluginLoader>
+#include <QDir>
+
+Q_LOGGING_CATEGORY(lcPluginLoader, "pluginLoader", QtInfoMsg)
+
+namespace OCC {
+
+PluginFactory::~PluginFactory() = default;
+
+QObject *PluginLoader::createInternal(const QString& type, const QString &name, QObject* parent)
+{
+ auto factory = load<PluginFactory>(type, name);
+ if (!factory) {
+ return nullptr;
+ } else {
+ return factory->create(parent);
+ }
+}
+
+QString PluginLoader::pluginName(const QString &type, const QString &name)
+{
+ return QString(QLatin1String("%1sync_%2_%3"))
+ .arg(APPLICATION_EXECUTABLE)
+ .arg(type)
+ .arg(name);
+}
+
+QObject *PluginLoader::loadPluginInternal(const QString& type, const QString &name)
+{
+ QString fileName = pluginName(type, name);
+ QPluginLoader pluginLoader(fileName);
+ auto plugin = pluginLoader.load();
+ if(plugin) {
+ qCInfo(lcPluginLoader) << "Loaded plugin" << fileName;
+ } else {
+ qCWarning(lcPluginLoader) << "Could not load plugin"
+ << fileName <<":"
+ << pluginLoader.errorString()
+ << "from" << QDir::currentPath();
+ }
+
+ return pluginLoader.instance();
+}
+
+}
--- /dev/null
+/*
+ * Copyright (C) by Dominik Schmidt <dschmidt@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "owncloudlib.h"
+#include <QObject>
+#include <QPluginLoader>
+
+namespace OCC {
+
+class OWNCLOUDSYNC_EXPORT PluginFactory
+{
+public:
+ ~PluginFactory();
+ virtual QObject* create(QObject* parent) = 0;
+};
+
+template<class PLUGIN_CLASS>
+class DefaultPluginFactory : public PluginFactory
+{
+public:
+ QObject* create(QObject* parent) override
+ {
+ return new PLUGIN_CLASS(parent);
+ }
+};
+
+class OWNCLOUDSYNC_EXPORT PluginLoader
+{
+public:
+ static QString pluginName(const QString &type, const QString &name);
+
+ template<class PLUGIN_CLASS, typename ... Args>
+ PLUGIN_CLASS *create(Args&& ... args)
+ {
+ return qobject_cast<PLUGIN_CLASS*>(createInternal(std::forward<Args>(args)...));
+ }
+
+private:
+ template<class FACTORY_CLASS, typename ... Args>
+ FACTORY_CLASS *load(Args&& ... args)
+ {
+ return qobject_cast<FACTORY_CLASS*>(loadPluginInternal(std::forward<Args>(args)...));
+ }
+
+ QObject *loadPluginInternal(const QString& type, const QString &name);
+ QObject *createInternal(const QString& type, const QString &name, QObject* parent = nullptr);
+};
+
+}
+
+Q_DECLARE_INTERFACE(OCC::PluginFactory, "org.owncloud.PluginFactory")
#include "common/asserts.h"
#include "clientsideencryptionjobs.h"
#include "propagatedownloadencrypted.h"
+#include "common/vfs.h"
#include <QLoggingCategory>
#include <QNetworkAccessManager>
}
// DOES NOT take ownership of the device.
-GETFileJob::GETFileJob(AccountPtr account, const QString &path, QFile *device,
+GETFileJob::GETFileJob(AccountPtr account, const QString &path, QIODevice *device,
const QMap<QByteArray, QByteArray> &headers, const QByteArray &expectedEtagForResume,
quint64 resumeStart, QObject *parent)
: AbstractNetworkJob(account, path, parent)
, _device(device)
, _headers(headers)
, _expectedEtagForResume(expectedEtagForResume)
+ , _expectedContentLength(-1)
+ , _contentLength(0)
, _resumeStart(resumeStart)
, _errorStatus(SyncFileItem::NoStatus)
, _bandwidthLimited(false)
{
}
-GETFileJob::GETFileJob(AccountPtr account, const QUrl &url, QFile *device,
+GETFileJob::GETFileJob(AccountPtr account, const QUrl &url, QIODevice *device,
const QMap<QByteArray, QByteArray> &headers, const QByteArray &expectedEtagForResume,
quint64 resumeStart, QObject *parent)
, _device(device)
, _headers(headers)
, _expectedEtagForResume(expectedEtagForResume)
+ , _expectedContentLength(-1)
+ , _contentLength(0)
, _resumeStart(resumeStart)
, _errorStatus(SyncFileItem::NoStatus)
, _directDownloadUrl(url)
return;
}
+ _contentLength = reply()->header(QNetworkRequest::ContentLengthHeader).toLongLong();
+ if (_expectedContentLength != -1 && _contentLength != _expectedContentLength) {
+ qCWarning(lcGetJob) << "We received a different content length than expected!"
+ << _expectedContentLength << "vs" << _contentLength;
+ _errorString = tr("We received an unexpected download Content-Length.");
+ _errorStatus = SyncFileItem::NormalError;
+ reply()->abort();
+ return;
+ }
+
quint64 start = 0;
QByteArray ranges = reply()->rawHeader("Content-Range");
if (!ranges.isEmpty()) {
{
_stopwatch.start();
+ auto &syncOptions = propagator()->syncOptions();
+ auto vfs = syncOptions._vfs;
+
// For virtual files just create the file and be done
+ if (_item->_type == ItemTypeVirtualFileDehydration) {
+ _item->_type = ItemTypeVirtualFile;
+ // TODO: Could dehydrate without wiping the file entirely
+ auto fn = propagator()->getFilePath(_item->_file);
+ qCDebug(lcPropagateDownload) << "dehydration: wiping base file" << fn;
+ QFile::remove(fn);
+ propagator()->_journal->deleteFileRecord(_item->_file);
+
+ if (vfs && vfs->mode() == Vfs::WithSuffix) {
+ // Normally new suffix-virtual files already have the suffix included in the path
+ // but for dehydrations that isn't the case. Adjust it here.
+ _item->_file.append(vfs->fileSuffix());
+ }
+ }
if (_item->_type == ItemTypeVirtualFile) {
auto fn = propagator()->getFilePath(_item->_file);
qCDebug(lcPropagateDownload) << "creating virtual file" << fn;
- // NOTE: Other places might depend on contents of placeholder files (like csync_update)
- QFile file(fn);
- file.open(QFile::ReadWrite | QFile::Truncate);
- file.write(" ");
- file.close();
- FileSystem::setModTime(fn, _item->_modtime);
+ ASSERT(vfs);
+ vfs->createPlaceholder(propagator()->_localDir, _item);
updateMetadata(false);
return;
}
// Don't keep the temporary file if it is empty or we
// used a bad range header or the file's not on the server anymore.
- if (_tmpFile.size() == 0 || badRangeHeader || fileNotFound) {
+ if (_tmpFile.exists() && (_tmpFile.size() == 0 || badRangeHeader || fileNotFound)) {
_tmpFile.close();
FileSystem::remove(_tmpFile.fileName());
propagator()->_journal->setDownloadInfo(_item->_file, SyncJournalDb::DownloadInfo());
done(SyncFileItem::SoftError, error);
return;
}
+
+ // Make the file a hydrated placeholder if possible
+ if (auto vfs = propagator()->syncOptions()._vfs) {
+ vfs->convertToPlaceholder(fn, _item);
+ }
+
FileSystem::setFileHidden(fn, false);
// Maybe we downloaded a newer version of the file than we thought we would...
if (_conflictRecord.isValid())
propagator()->_journal->setConflictRecord(_conflictRecord);
- // If we downloaded something that used to be a virtual file,
- // wipe the virtual file and its db entry now that we're done.
if (_item->_type == ItemTypeVirtualFileDownload) {
- auto virtualFile = propagator()->addVirtualFileSuffix(_item->_file);
- auto fn = propagator()->getFilePath(virtualFile);
- qCDebug(lcPropagateDownload) << "Download of previous virtual file finished" << fn;
- QFile::remove(fn);
- propagator()->_journal->deleteFileRecord(virtualFile);
+ // A downloaded virtual file becomes normal
_item->_type = ItemTypeFile;
+
+ // If the virtual file used to have a different name and db
+ // entry, wipe both now.
+ auto vfs = propagator()->syncOptions()._vfs;
+ if (vfs && vfs->mode() == Vfs::WithSuffix) {
+ QString virtualFile = _item->_file + vfs->fileSuffix();
+ auto fn = propagator()->getFilePath(virtualFile);
+ qCDebug(lcPropagateDownload) << "Download of previous virtual file finished" << fn;
+ QFile::remove(fn);
+ propagator()->_journal->deleteFileRecord(virtualFile);
+ }
}
updateMetadata(isConflict);
class GETFileJob : public AbstractNetworkJob
{
Q_OBJECT
- QFile *_device;
+ QIODevice *_device;
QMap<QByteArray, QByteArray> _headers;
QString _errorString;
QByteArray _expectedEtagForResume;
+ qint64 _expectedContentLength;
+ quint64 _contentLength;
quint64 _resumeStart;
SyncFileItem::Status _errorStatus;
QUrl _directDownloadUrl;
public:
// DOES NOT take ownership of the device.
- explicit GETFileJob(AccountPtr account, const QString &path, QFile *device,
+ explicit GETFileJob(AccountPtr account, const QString &path, QIODevice *device,
const QMap<QByteArray, QByteArray> &headers, const QByteArray &expectedEtagForResume,
quint64 resumeStart, QObject *parent = nullptr);
// For directDownloadUrl:
- explicit GETFileJob(AccountPtr account, const QUrl &url, QFile *device,
+ explicit GETFileJob(AccountPtr account, const QUrl &url, QIODevice *device,
const QMap<QByteArray, QByteArray> &headers, const QByteArray &expectedEtagForResume,
quint64 resumeStart, QObject *parent = nullptr);
virtual ~GETFileJob()
quint64 resumeStart() { return _resumeStart; }
time_t lastModified() { return _lastModified; }
+ quint64 contentLength() const { return _contentLength; }
+ qint64 expectedContentLength() const { return _expectedContentLength; }
+ void setExpectedContentLength(qint64 size) { _expectedContentLength = size; }
signals:
void finishedSignal();
QString source = propagator()->_remoteFolder + _item->_file;
QString destination = QDir::cleanPath(propagator()->account()->davUrl().path() + propagator()->_remoteFolder + _item->_renameTarget);
- if (_item->_type == ItemTypeVirtualFile || _item->_type == ItemTypeVirtualFileDownload) {
- auto suffix = propagator()->syncOptions()._virtualFileSuffix;
+ auto vfs = propagator()->syncOptions()._vfs;
+ if (vfs && vfs->mode() == Vfs::WithSuffix
+ && (_item->_type == ItemTypeVirtualFile || _item->_type == ItemTypeVirtualFileDownload)) {
+ const auto suffix = vfs->fileSuffix();
ASSERT(source.endsWith(suffix) && destination.endsWith(suffix));
if (source.endsWith(suffix) && destination.endsWith(suffix)) {
source.chop(suffix.size());
record._path = _item->_renameTarget.toUtf8();
if (oldRecord.isValid()) {
record._checksumHeader = oldRecord._checksumHeader;
+ record._type = oldRecord._type;
if (record._fileSize != oldRecord._fileSize) {
qCWarning(lcPropagateRemoteMove) << "File sizes differ on server vs sync journal: " << record._fileSize << oldRecord._fileSize;
#include "common/asserts.h"
#include "configfile.h"
#include "discovery.h"
+#include "common/vfs.h"
#ifdef Q_OS_WIN
#include <windows.h>
rec._serverHasIgnoredFiles |= prev._serverHasIgnoredFiles;
_journal->setFileRecord(rec);
+ // ### Update vfs metadata with Vfs::updateMetadata()
+
// This might have changed the shared flag, so we must notify SyncFileStatusTracker for example
emit itemCompleted(item);
} else {
_lastLocalDiscoveryStyle = _localDiscoveryStyle;
- if (_syncOptions._newFilesAreVirtual && _syncOptions._virtualFileSuffix.isEmpty()) {
- syncError(tr("Using virtual files but suffix is not set"));
- finalize(false);
- return;
+ if (_syncOptions._vfs && _syncOptions._vfs->mode() == Vfs::WithSuffix) {
+ if (_syncOptions._vfs->fileSuffix().isEmpty()) {
+ syncError(tr("Using virtual files with suffix, but suffix is not set"));
+ finalize(false);
+ return;
+ }
}
// If needed, make sure we have up to date E2E information before the
return false;
}
+void SyncEngine::wipeVirtualFiles(const QString &localPath, SyncJournalDb &journal, Vfs *vfs)
+{
+ qCInfo(lcEngine) << "Wiping virtual files inside" << localPath;
+ journal.getFilesBelowPath(QByteArray(), [&](const SyncJournalFileRecord &rec) {
+ if (rec._type != ItemTypeVirtualFile && rec._type != ItemTypeVirtualFileDownload)
+ return;
+
+ qCDebug(lcEngine) << "Removing db record for" << rec._path;
+ journal.deleteFileRecord(rec._path);
+
+ // If the local file is a dehydrated placeholder, wipe it too.
+ // Otherwise leave it to allow the next sync to have a new-new conflict.
+ QString localFile = localPath + rec._path;
+ if (QFile::exists(localFile) && vfs && vfs->isDehydratedPlaceholder(localFile)) {
+ qCDebug(lcEngine) << "Removing local dehydrated placeholder" << rec._path;
+ QFile::remove(localFile);
+ }
+ });
+
+ journal.forceRemoteDiscoveryNextSync();
+
+ // Postcondition: No ItemTypeVirtualFile / ItemTypeVirtualFileDownload left in the db
+}
+
void SyncEngine::abort()
{
if (_propagator)
/** Access the last sync run's local discovery style */
LocalDiscoveryStyle lastLocalDiscoveryStyle() const { return _lastLocalDiscoveryStyle; }
+ /** Removes all virtual file db entries and dehydrated local placeholders.
+ *
+ * Particularly useful when switching off vfs mode or switching to a
+ * different kind of vfs.
+ */
+ static void wipeVirtualFiles(const QString &localPath, SyncJournalDb &journal, Vfs *vfs);
+
auto getPropagator() { return _propagator; } // for the test
signals:
#include "syncfileitem.h"
#include "common/syncjournalfilerecord.h"
#include "common/utility.h"
+#include "filesystem.h"
#include <QLoggingCategory>
#include "csync/vio/csync_vio_local.h"
rec._checksumHeader = _checksumHeader;
rec._e2eMangledName = _encryptedFileName.toUtf8();
- // Go through csync vio just to get the inode.
- csync_file_stat_t fs;
- if (csync_vio_local_stat(localFileName.toUtf8().constData(), &fs) == 0) {
- rec._inode = fs.inode;
- qCDebug(lcFileItem) << localFileName << "Retrieved inode " << _inode << "(previous item inode: " << _inode << ")";
+ // Update the inode if possible
+ rec._inode = _inode;
+ if (FileSystem::getInode(localFileName, &rec._inode)) {
+ qCDebug(lcFileItem) << localFileName << "Retrieved inode " << rec._inode << "(previous item inode: " << _inode << ")";
} else {
// use the "old" inode coming with the item for the case where the
// filesystem stat fails. That can happen if the the file was removed
// or renamed meanwhile. For the rename case we still need the inode to
// detect the rename though.
- rec._inode = _inode;
qCWarning(lcFileItem) << "Failed to query the 'inode' for file " << localFileName;
}
return rec;
#include "owncloudlib.h"
#include <QString>
#include <chrono>
-
+#include "common/vfs.h"
namespace OCC {
bool _moveFilesToTrash = false;
/** Create a virtual file for new files instead of downloading */
- bool _newFilesAreVirtual = false;
- QString _virtualFileSuffix = ".owncloud";
+ Vfs *_vfs = nullptr;
/** The initial un-adjusted chunk size in bytes for chunked uploads, both
* for old and new chunking algorithm, which classifies the item to be chunked
--- /dev/null
+### TODO: Find plugins dynamically
+list(APPEND vfsPlugins "suffix")
+
+foreach(vfsPlugin ${vfsPlugins})
+ message(STATUS "Add vfsPlugin in dir: ${vfsPlugin}")
+ add_subdirectory("${vfsPlugin}")
+
+ if(UNIT_TESTING AND IS_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/${vfsPlugin}/test")
+ message(STATUS "Add vfsPlugin tests in dir: ${vfsPlugin}")
+ add_subdirectory("${vfsPlugin}/test" "${vfsPlugin}_test")
+ endif()
+endforeach()
--- /dev/null
+add_library("${synclib_NAME}_vfs_suffix" SHARED
+ vfs_suffix.cpp
+)
+
+target_link_libraries("${synclib_NAME}_vfs_suffix"
+ "${synclib_NAME}"
+)
+
+set_target_properties("${synclib_NAME}_vfs_suffix" PROPERTIES
+ LIBRARY_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY}
+ RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY}
+ PREFIX ""
+ AUTOMOC TRUE
+)
+
--- /dev/null
+/*
+ * Copyright (C) by Christian Kamm <mail@ckamm.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "vfs_suffix.h"
+
+#include <QFile>
+
+#include "syncfileitem.h"
+#include "filesystem.h"
+
+namespace OCC {
+
+class VfsSuffixPrivate
+{
+};
+
+VfsSuffix::VfsSuffix(QObject *parent)
+ : Vfs(parent)
+ , d_ptr(new VfsSuffixPrivate)
+{
+}
+
+VfsSuffix::~VfsSuffix()
+{
+}
+
+Vfs::Mode VfsSuffix::mode() const
+{
+ return WithSuffix;
+}
+
+QString VfsSuffix::fileSuffix() const
+{
+ return QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
+}
+
+void VfsSuffix::registerFolder(const VfsSetupParams &)
+{
+}
+
+void VfsSuffix::start(const VfsSetupParams &)
+{
+}
+
+void VfsSuffix::stop()
+{
+}
+
+void VfsSuffix::unregisterFolder()
+{
+}
+
+bool VfsSuffix::isHydrating() const
+{
+ return false;
+}
+
+bool VfsSuffix::updateMetadata(const QString &filePath, time_t modtime, quint64, const QByteArray &, QString *)
+{
+ FileSystem::setModTime(filePath, modtime);
+ return true;
+}
+
+void VfsSuffix::createPlaceholder(const QString &syncFolder, const SyncFileItemPtr &item)
+{
+ // NOTE: Other places might depend on contents of placeholder files (like csync_update)
+ QString fn = syncFolder + item->_file;
+ QFile file(fn);
+ file.open(QFile::ReadWrite | QFile::Truncate);
+ file.write(" ");
+ file.close();
+ FileSystem::setModTime(fn, item->_modtime);
+}
+
+void VfsSuffix::convertToPlaceholder(const QString &, const SyncFileItemPtr &)
+{
+ // Nothing necessary
+}
+
+bool VfsSuffix::isDehydratedPlaceholder(const QString &filePath)
+{
+ if (!filePath.endsWith(fileSuffix()))
+ return false;
+ QFileInfo fi(filePath);
+ return fi.exists() && fi.size() == 1;
+}
+
+bool VfsSuffix::statTypeVirtualFile(csync_file_stat_t *stat, void *)
+{
+ if (stat->path.endsWith(fileSuffix().toUtf8())) {
+ stat->type = ItemTypeVirtualFile;
+ return true;
+ }
+ return false;
+}
+
+} // namespace OCC
--- /dev/null
+/*
+ * Copyright (C) by Christian Kamm <mail@ckamm.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#pragma once
+
+#include <QObject>
+#include <QScopedPointer>
+
+#include "common/vfs.h"
+#include "plugin.h"
+
+namespace OCC {
+
+class VfsSuffixPrivate;
+
+class VfsSuffix : public Vfs
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(VfsSuffix)
+ const QScopedPointer<VfsSuffixPrivate> d_ptr;
+
+public:
+ explicit VfsSuffix(QObject *parent = nullptr);
+ ~VfsSuffix();
+
+ Mode mode() const override;
+ QString fileSuffix() const override;
+
+ void registerFolder(const VfsSetupParams ¶ms) override;
+ void start(const VfsSetupParams ¶ms) override;
+ void stop() override;
+ void unregisterFolder() override;
+
+ bool isHydrating() const override;
+
+ bool updateMetadata(const QString &filePath, time_t modtime, quint64 size, const QByteArray &fileId, QString *error) override;
+
+ void createPlaceholder(const QString &syncFolder, const SyncFileItemPtr &item) override;
+ void convertToPlaceholder(const QString &filename, const SyncFileItemPtr &item) override;
+
+ bool isDehydratedPlaceholder(const QString &filePath) override;
+ bool statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) override;
+};
+
+class SuffixVfsPluginFactory : public QObject, public DefaultPluginFactory<VfsSuffix>
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.owncloud.PluginFactory")
+ Q_INTERFACES(OCC::PluginFactory)
+};
+
+} // namespace OCC
add_definitions(-DOWNCLOUD_TEST)
add_definitions(-DOWNCLOUD_BIN_PATH="${CMAKE_BINARY_DIR}/bin")
- add_test(NAME ${OWNCLOUD_TEST_CLASS}Test COMMAND ${OWNCLOUD_TEST_CLASS}Test)
+ message(STATUS "Add test: ${OWNCLOUD_TEST_CLASS}Test")
+ add_test(NAME ${OWNCLOUD_TEST_CLASS}Test
+ COMMAND ${OWNCLOUD_TEST_CLASS}Test
+ WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
+
+ target_include_directories(${OWNCLOUD_TEST_CLASS}Test PRIVATE "${CMAKE_SOURCE_DIR}/test/")
endmacro()
macro(nextcloud_add_benchmark test_class additional_cpp)
syncOnce();
}
+ OCC::AccountPtr account() const { return _account; }
OCC::SyncEngine &syncEngine() const { return *_syncEngine; }
OCC::SyncJournalDb &syncJournal() const { return *_journalDb; }
#include <QtTest>
#include "syncenginetestutils.h"
+#include "common/vfs.h"
+#include "plugin.h"
#include <syncengine.h>
using namespace OCC;
{
auto &journal = folder.syncJournal();
SyncJournalFileRecord record;
- journal.getFileRecord(path + ".owncloud", &record);
+ journal.getFileRecord(path + ".nextcloud", &record);
if (!record.isValid())
return;
record._type = ItemTypeVirtualFileDownload;
journal.avoidReadFromDbOnNextSync(record._path);
}
+void markForDehydration(FakeFolder &folder, const QByteArray &path)
+{
+ auto &journal = folder.syncJournal();
+ SyncJournalFileRecord record;
+ journal.getFileRecord(path, &record);
+ if (!record.isValid())
+ return;
+ record._type = ItemTypeVirtualFileDehydration;
+ journal.setFileRecord(record);
+ journal.avoidReadFromDbOnNextSync(record._path);
+}
+
+SyncOptions vfsSyncOptions()
+{
+ SyncOptions options;
+ options._vfs = PluginLoader().create<Vfs>("vfs", "suffix");
+ return options;
+}
+
class TestSyncVirtualFiles : public QObject
{
Q_OBJECT
QFETCH(bool, doLocalDiscovery);
FakeFolder fakeFolder{ FileInfo() };
- SyncOptions syncOptions;
- syncOptions._newFilesAreVirtual = true;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
fakeFolder.remoteModifier().setModTime("A/a1", someDate);
QVERIFY(fakeFolder.syncOnce());
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
- QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1.owncloud").lastModified(), someDate);
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1.nextcloud").lastModified(), someDate);
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
- QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NEW));
- QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
+ QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NEW));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
cleanup();
// Another sync doesn't actually lead to changes
QVERIFY(fakeFolder.syncOnce());
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
- QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1.owncloud").lastModified(), someDate);
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1.nextcloud").lastModified(), someDate);
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
- QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
QVERIFY(completeSpy.isEmpty());
cleanup();
fakeFolder.syncJournal().forceRemoteDiscoveryNextSync();
QVERIFY(fakeFolder.syncOnce());
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
- QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1.owncloud").lastModified(), someDate);
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1.nextcloud").lastModified(), someDate);
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
- QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
QVERIFY(completeSpy.isEmpty());
cleanup();
fakeFolder.remoteModifier().appendByte("A/a1");
QVERIFY(fakeFolder.syncOnce());
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
- QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_UPDATE_METADATA));
- QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
- QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._fileSize, 65);
+ QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_UPDATE_METADATA));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._fileSize, 65);
cleanup();
// If the local virtual file file is removed, it'll just be recreated
if (!doLocalDiscovery)
fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, { "A" });
- fakeFolder.localModifier().remove("A/a1.owncloud");
+ fakeFolder.localModifier().remove("A/a1.nextcloud");
QVERIFY(fakeFolder.syncOnce());
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
- QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NEW));
- QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
- QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._fileSize, 65);
+ QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NEW));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._fileSize, 65);
cleanup();
// Remote rename is propagated
QVERIFY(fakeFolder.syncOnce());
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
QVERIFY(!fakeFolder.currentLocalState().find("A/a1m"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a1m.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1m.nextcloud"));
QVERIFY(!fakeFolder.currentRemoteState().find("A/a1"));
QVERIFY(fakeFolder.currentRemoteState().find("A/a1m"));
QVERIFY(
- itemInstruction(completeSpy, "A/a1m.owncloud", CSYNC_INSTRUCTION_RENAME)
- || (itemInstruction(completeSpy, "A/a1m.owncloud", CSYNC_INSTRUCTION_NEW)
- && itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_REMOVE)));
- QCOMPARE(dbRecord(fakeFolder, "A/a1m.owncloud")._type, ItemTypeVirtualFile);
+ itemInstruction(completeSpy, "A/a1m.nextcloud", CSYNC_INSTRUCTION_RENAME)
+ || (itemInstruction(completeSpy, "A/a1m.nextcloud", CSYNC_INSTRUCTION_NEW)
+ && itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_REMOVE)));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1m.nextcloud")._type, ItemTypeVirtualFile);
cleanup();
// Remote remove is propagated
fakeFolder.remoteModifier().remove("A/a1m");
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(!fakeFolder.currentLocalState().find("A/a1m.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1m.nextcloud"));
QVERIFY(!fakeFolder.currentRemoteState().find("A/a1m"));
- QVERIFY(itemInstruction(completeSpy, "A/a1m.owncloud", CSYNC_INSTRUCTION_REMOVE));
- QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
- QVERIFY(!dbRecord(fakeFolder, "A/a1m.owncloud").isValid());
+ QVERIFY(itemInstruction(completeSpy, "A/a1m.nextcloud", CSYNC_INSTRUCTION_REMOVE));
+ QVERIFY(!dbRecord(fakeFolder, "A/a1.nextcloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a1m.nextcloud").isValid());
cleanup();
// Edge case: Local virtual file but no db entry for some reason
fakeFolder.remoteModifier().insert("A/a2", 64);
fakeFolder.remoteModifier().insert("A/a3", 64);
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a3.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a2.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a3.nextcloud"));
cleanup();
- fakeFolder.syncEngine().journal()->deleteFileRecord("A/a2.owncloud");
- fakeFolder.syncEngine().journal()->deleteFileRecord("A/a3.owncloud");
+ fakeFolder.syncEngine().journal()->deleteFileRecord("A/a2.nextcloud");
+ fakeFolder.syncEngine().journal()->deleteFileRecord("A/a3.nextcloud");
fakeFolder.remoteModifier().remove("A/a3");
fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly);
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
- QVERIFY(itemInstruction(completeSpy, "A/a2.owncloud", CSYNC_INSTRUCTION_UPDATE_METADATA));
- QVERIFY(dbRecord(fakeFolder, "A/a2.owncloud").isValid());
- QVERIFY(!fakeFolder.currentLocalState().find("A/a3.owncloud"));
- QVERIFY(itemInstruction(completeSpy, "A/a3.owncloud", CSYNC_INSTRUCTION_REMOVE));
- QVERIFY(!dbRecord(fakeFolder, "A/a3.owncloud").isValid());
+ QVERIFY(fakeFolder.currentLocalState().find("A/a2.nextcloud"));
+ QVERIFY(itemInstruction(completeSpy, "A/a2.nextcloud", CSYNC_INSTRUCTION_UPDATE_METADATA));
+ QVERIFY(dbRecord(fakeFolder, "A/a2.nextcloud").isValid());
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a3.nextcloud"));
+ QVERIFY(itemInstruction(completeSpy, "A/a3.nextcloud", CSYNC_INSTRUCTION_REMOVE));
+ QVERIFY(!dbRecord(fakeFolder, "A/a3.nextcloud").isValid());
cleanup();
}
void testVirtualFileConflict()
{
FakeFolder fakeFolder{ FileInfo() };
- SyncOptions syncOptions;
- syncOptions._newFilesAreVirtual = true;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
fakeFolder.remoteModifier().mkdir("C");
fakeFolder.remoteModifier().insert("C/c1", 64);
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("B/b2.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("B/b2.nextcloud"));
cleanup();
// A: the correct file and a conflicting file are added, virtual files stay
fakeFolder.localModifier().insert("A/a2", 30);
fakeFolder.localModifier().insert("B/b1", 64);
fakeFolder.localModifier().insert("B/b2", 30);
- fakeFolder.localModifier().remove("B/b1.owncloud");
- fakeFolder.localModifier().remove("B/b2.owncloud");
+ fakeFolder.localModifier().remove("B/b1.nextcloud");
+ fakeFolder.localModifier().remove("B/b2.nextcloud");
fakeFolder.localModifier().mkdir("C/c1");
fakeFolder.localModifier().insert("C/c1/foo");
QVERIFY(fakeFolder.syncOnce());
QVERIFY(itemInstruction(completeSpy, "C/c1", CSYNC_INSTRUCTION_CONFLICT));
// no virtual file files should remain
- QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/a2.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("B/b1.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("B/b2.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("C/c1.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a2.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("B/b1.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("B/b2.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("C/c1.nextcloud"));
// conflict files should exist
QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 3);
QCOMPARE(dbRecord(fakeFolder, "B/b1")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "B/b2")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "C/c1")._type, ItemTypeFile);
- QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
- QVERIFY(!dbRecord(fakeFolder, "A/a2.owncloud").isValid());
- QVERIFY(!dbRecord(fakeFolder, "B/b1.owncloud").isValid());
- QVERIFY(!dbRecord(fakeFolder, "B/b2.owncloud").isValid());
- QVERIFY(!dbRecord(fakeFolder, "C/c1.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a1.nextcloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a2.nextcloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "B/b1.nextcloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "B/b2.nextcloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "C/c1.nextcloud").isValid());
cleanup();
}
void testWithNormalSync()
{
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- SyncOptions syncOptions;
- syncOptions._newFilesAreVirtual = true;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
fakeFolder.remoteModifier().insert("A/new");
QVERIFY(fakeFolder.syncOnce());
QVERIFY(!fakeFolder.currentLocalState().find("A/new"));
- QVERIFY(fakeFolder.currentLocalState().find("A/new.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/new.nextcloud"));
QVERIFY(fakeFolder.currentRemoteState().find("A/new"));
- QVERIFY(itemInstruction(completeSpy, "A/new.owncloud", CSYNC_INSTRUCTION_NEW));
- QCOMPARE(dbRecord(fakeFolder, "A/new.owncloud")._type, ItemTypeVirtualFile);
+ QVERIFY(itemInstruction(completeSpy, "A/new.nextcloud", CSYNC_INSTRUCTION_NEW));
+ QCOMPARE(dbRecord(fakeFolder, "A/new.nextcloud")._type, ItemTypeVirtualFile);
cleanup();
}
void testVirtualFileDownload()
{
FakeFolder fakeFolder{ FileInfo() };
- SyncOptions syncOptions;
- syncOptions._newFilesAreVirtual = true;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
fakeFolder.remoteModifier().insert("A/a5");
fakeFolder.remoteModifier().insert("A/a6");
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a3.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a4.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a5.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a6.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a2.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a3.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a4.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a5.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a6.nextcloud"));
cleanup();
// Download by changing the db entry
fakeFolder.remoteModifier().rename("A/a4", "A/a4m");
fakeFolder.localModifier().insert("A/a5");
fakeFolder.localModifier().insert("A/a6");
- fakeFolder.localModifier().remove("A/a6.owncloud");
+ fakeFolder.localModifier().remove("A/a6.nextcloud");
QVERIFY(fakeFolder.syncOnce());
QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
- QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NONE));
+ QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NONE));
QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_NEW));
- QVERIFY(itemInstruction(completeSpy, "A/a2.owncloud", CSYNC_INSTRUCTION_NONE));
- QVERIFY(itemInstruction(completeSpy, "A/a3.owncloud", CSYNC_INSTRUCTION_REMOVE));
+ QVERIFY(itemInstruction(completeSpy, "A/a2.nextcloud", CSYNC_INSTRUCTION_NONE));
+ QVERIFY(itemInstruction(completeSpy, "A/a3.nextcloud", CSYNC_INSTRUCTION_REMOVE));
QVERIFY(itemInstruction(completeSpy, "A/a4m", CSYNC_INSTRUCTION_NEW));
- QVERIFY(itemInstruction(completeSpy, "A/a4.owncloud", CSYNC_INSTRUCTION_REMOVE));
+ QVERIFY(itemInstruction(completeSpy, "A/a4.nextcloud", CSYNC_INSTRUCTION_REMOVE));
QVERIFY(itemInstruction(completeSpy, "A/a5", CSYNC_INSTRUCTION_CONFLICT));
- QVERIFY(itemInstruction(completeSpy, "A/a5.owncloud", CSYNC_INSTRUCTION_NONE));
+ QVERIFY(itemInstruction(completeSpy, "A/a5.nextcloud", CSYNC_INSTRUCTION_NONE));
QVERIFY(itemInstruction(completeSpy, "A/a6", CSYNC_INSTRUCTION_CONFLICT));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "A/a4m")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "A/a5")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "A/a6")._type, ItemTypeFile);
- QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
- QVERIFY(!dbRecord(fakeFolder, "A/a2.owncloud").isValid());
- QVERIFY(!dbRecord(fakeFolder, "A/a3.owncloud").isValid());
- QVERIFY(!dbRecord(fakeFolder, "A/a4.owncloud").isValid());
- QVERIFY(!dbRecord(fakeFolder, "A/a5.owncloud").isValid());
- QVERIFY(!dbRecord(fakeFolder, "A/a6.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a1.nextcloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a2.nextcloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a3.nextcloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a4.nextcloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a5.nextcloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a6.nextcloud").isValid());
}
void testVirtualFileDownloadResume()
{
FakeFolder fakeFolder{ FileInfo() };
- SyncOptions syncOptions;
- syncOptions._newFilesAreVirtual = true;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
fakeFolder.remoteModifier().mkdir("A");
fakeFolder.remoteModifier().insert("A/a1");
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
cleanup();
// Download by changing the db entry
fakeFolder.serverErrorPaths().append("A/a1", 500);
QVERIFY(!fakeFolder.syncOnce());
QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
- QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NONE));
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NONE));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
- QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFileDownload);
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFileDownload);
QVERIFY(!dbRecord(fakeFolder, "A/a1").isValid());
cleanup();
fakeFolder.serverErrorPaths().clear();
QVERIFY(fakeFolder.syncOnce());
QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
- QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NONE));
+ QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NONE));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
- QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a1.nextcloud").isValid());
}
- // Check what might happen if an older sync client encounters virtual files
- void testOldVersion1()
+ // Check what happens if vfs mode is disabled
+ void testSwitchOfVfs()
{
QSKIP("Does not work with the new discovery because the way we simulate the old client does not work");
FakeFolder fakeFolder{ FileInfo() };
- SyncOptions syncOptions;
- syncOptions._newFilesAreVirtual = true;
+ SyncOptions syncOptions = vfsSyncOptions();
fakeFolder.syncEngine().setSyncOptions(syncOptions);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
fakeFolder.remoteModifier().mkdir("A");
fakeFolder.remoteModifier().insert("A/a1");
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
- // Simulate an old client by switching the type of all ItemTypeVirtualFile
- // entries in the db to an invalid type.
- auto &db = fakeFolder.syncJournal();
- SyncJournalFileRecord rec;
- db.getFileRecord(QByteArray("A/a1.owncloud"), &rec);
- QVERIFY(rec.isValid());
- QCOMPARE(rec._type, ItemTypeVirtualFile);
- rec._type = static_cast<ItemType>(-1);
- db.setFileRecord(rec);
-
- // Also switch off new files becoming virtual files
- syncOptions._newFilesAreVirtual = false;
+ // Switch off new files becoming virtual files
+ syncOptions._vfs = nullptr;
fakeFolder.syncEngine().setSyncOptions(syncOptions);
- // A sync that doesn't do remote discovery has no effect
+ // A sync that doesn't do remote discovery will wipe the placeholder, but not redownload
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
- QVERIFY(!fakeFolder.currentRemoteState().find("A/a1.owncloud"));
+ QVERIFY(!fakeFolder.currentRemoteState().find("A/a1.nextcloud"));
// But with a remote discovery the virtual files will be removed and
// the remote files will be downloaded.
- db.forceRemoteDiscoveryNextSync();
+ fakeFolder.syncJournal().forceRemoteDiscoveryNextSync();
QVERIFY(fakeFolder.syncOnce());
QVERIFY(fakeFolder.currentLocalState().find("A/a1"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
- // Older versions may leave db entries for foo and foo.owncloud
+ // Older versions may leave db entries for foo and foo.nextcloud
void testOldVersion2()
{
QSKIP("Does not work with the new discovery because the way we simulate the old client does not work");
// Create the virtual file too
// In the wild, the new version would create the virtual file and the db entry
// while the old version would download the plain file.
- fakeFolder.localModifier().insert("A/a1.owncloud");
+ fakeFolder.localModifier().insert("A/a1.nextcloud");
auto &db = fakeFolder.syncJournal();
SyncJournalFileRecord rec;
db.getFileRecord(QByteArray("A/a1"), &rec);
rec._type = ItemTypeVirtualFile;
- rec._path = "A/a1.owncloud";
+ rec._path = "A/a1.nextcloud";
db.setFileRecord(rec);
- SyncOptions syncOptions;
- syncOptions._newFilesAreVirtual = true;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
// Check that a sync removes the virtual file and its db entry
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a1.nextcloud").isValid());
}
void testDownloadRecursive()
{
FakeFolder fakeFolder{ FileInfo() };
- SyncOptions syncOptions;
- syncOptions._newFilesAreVirtual = true;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
// Create a virtual file for remote files
fakeFolder.remoteModifier().insert("B/b1");
fakeFolder.remoteModifier().insert("B/Sub/b2");
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a3.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a4.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a5.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/Sub2/a6.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("B/b1.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a2.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a3.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a4.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a5.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/Sub2/a6.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("B/b1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2.nextcloud"));
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
QVERIFY(!fakeFolder.currentLocalState().find("A/a2"));
QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3"));
fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("A/Sub");
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a4.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a5.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/Sub2/a6.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("B/b1.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a2.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a4.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a5.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/Sub2/a6.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("B/b1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2.nextcloud"));
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
QVERIFY(!fakeFolder.currentLocalState().find("A/a2"));
QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a3"));
// Currently, this continue to add it as a virtual file.
fakeFolder.remoteModifier().insert("A/Sub/SubSub/a7");
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a7.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a7.nextcloud"));
QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a7"));
// Now download all files in "A"
fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("A");
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/a2.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a4.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a5.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/Sub2/a6.owncloud"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a7.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("B/b1.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a2.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a4.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a5.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/Sub2/a6.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a7.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("B/b1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2.nextcloud"));
QVERIFY(fakeFolder.currentLocalState().find("A/a1"));
QVERIFY(fakeFolder.currentLocalState().find("A/a2"));
QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a3"));
void testRenameToVirtual()
{
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
- SyncOptions syncOptions;
- syncOptions._newFilesAreVirtual = true;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
};
cleanup();
- // If a file is renamed to <name>.owncloud, it becomes virtual
- fakeFolder.localModifier().rename("A/a1", "A/a1.owncloud");
- // If a file is renamed to <random>.owncloud, the file sticks around (to preserve user data)
- fakeFolder.localModifier().rename("A/a2", "A/rand.owncloud");
+ // If a file is renamed to <name>.nextcloud, it becomes virtual
+ fakeFolder.localModifier().rename("A/a1", "A/a1.nextcloud");
+ // If a file is renamed to <random>.nextcloud, the file sticks around (to preserve user data)
+ fakeFolder.localModifier().rename("A/a2", "A/rand.nextcloud");
// dangling virtual files are removed
- fakeFolder.localModifier().insert("A/dangling.owncloud", 1, ' ');
+ fakeFolder.localModifier().insert("A/dangling.nextcloud", 1, ' ');
QVERIFY(fakeFolder.syncOnce());
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
- QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
- QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NEW));
- QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypeVirtualFile);
+ QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NEW));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
QVERIFY(!fakeFolder.currentLocalState().find("A/a2"));
- QVERIFY(!fakeFolder.currentLocalState().find("A/a2.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("A/rand.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a2.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/rand.nextcloud"));
QVERIFY(!fakeFolder.currentRemoteState().find("A/a2"));
QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_REMOVE));
- QVERIFY(!dbRecord(fakeFolder, "A/rand.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/rand.nextcloud").isValid());
- QVERIFY(!fakeFolder.currentLocalState().find("A/dangling.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/dangling.nextcloud"));
cleanup();
}
void testRenameVirtual()
{
FakeFolder fakeFolder{ FileInfo() };
- SyncOptions syncOptions;
- syncOptions._newFilesAreVirtual = true;
- fakeFolder.syncEngine().setSyncOptions(syncOptions);
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
fakeFolder.remoteModifier().insert("file2", 256, 'C');
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(fakeFolder.currentLocalState().find("file1.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("file2.owncloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("file1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("file2.nextcloud"));
cleanup();
- fakeFolder.localModifier().rename("file1.owncloud", "renamed1.owncloud");
- fakeFolder.localModifier().rename("file2.owncloud", "renamed2.owncloud");
+ fakeFolder.localModifier().rename("file1.nextcloud", "renamed1.nextcloud");
+ fakeFolder.localModifier().rename("file2.nextcloud", "renamed2.nextcloud");
triggerDownload(fakeFolder, "file2");
QVERIFY(fakeFolder.syncOnce());
- QVERIFY(!fakeFolder.currentLocalState().find("file1.owncloud"));
- QVERIFY(fakeFolder.currentLocalState().find("renamed1.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("file1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("renamed1.nextcloud"));
QVERIFY(!fakeFolder.currentRemoteState().find("file1"));
QVERIFY(fakeFolder.currentRemoteState().find("renamed1"));
- QVERIFY(itemInstruction(completeSpy, "renamed1.owncloud", CSYNC_INSTRUCTION_RENAME));
- QVERIFY(dbRecord(fakeFolder, "renamed1.owncloud").isValid());
+ QVERIFY(itemInstruction(completeSpy, "renamed1.nextcloud", CSYNC_INSTRUCTION_RENAME));
+ QVERIFY(dbRecord(fakeFolder, "renamed1.nextcloud").isValid());
// file2 has a conflict between the download request and the rename:
// currently the download wins
- QVERIFY(!fakeFolder.currentLocalState().find("file2.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("file2.nextcloud"));
QVERIFY(fakeFolder.currentLocalState().find("file2"));
QVERIFY(fakeFolder.currentRemoteState().find("file2"));
QVERIFY(itemInstruction(completeSpy, "file2", CSYNC_INSTRUCTION_NEW));
QVERIFY(dbRecord(fakeFolder, "file2").isValid());
}
+
+ // Dehydration via sync works
+ void testSyncDehydration()
+ {
+ FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
+
+ QVERIFY(fakeFolder.syncOnce());
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+
+ QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
+ auto cleanup = [&]() {
+ completeSpy.clear();
+ };
+ cleanup();
+
+ //
+ // Mark for dehydration and check
+ //
+
+ markForDehydration(fakeFolder, "A/a1");
+
+ markForDehydration(fakeFolder, "A/a2");
+ fakeFolder.remoteModifier().appendByte("A/a2");
+ // expect: normal dehydration
+
+ markForDehydration(fakeFolder, "B/b1");
+ fakeFolder.remoteModifier().remove("B/b1");
+ // expect: local removal
+
+ markForDehydration(fakeFolder, "B/b2");
+ fakeFolder.remoteModifier().rename("B/b2", "B/b3");
+ // expect: B/b2 is gone, B/b3 is NEW placeholder
+
+ markForDehydration(fakeFolder, "C/c1");
+ fakeFolder.localModifier().appendByte("C/c1");
+ // expect: no dehydration, upload of c1
+
+ markForDehydration(fakeFolder, "C/c2");
+ fakeFolder.localModifier().appendByte("C/c2");
+ fakeFolder.remoteModifier().appendByte("C/c2");
+ fakeFolder.remoteModifier().appendByte("C/c2");
+ // expect: no dehydration, conflict
+
+ QVERIFY(fakeFolder.syncOnce());
+
+ auto isDehydrated = [&](const QString &path) {
+ QString placeholder = path + ".nextcloud";
+ return !fakeFolder.currentLocalState().find(path)
+ && fakeFolder.currentLocalState().find(placeholder);
+ };
+
+ QVERIFY(isDehydrated("A/a1"));
+ QVERIFY(isDehydrated("A/a2"));
+
+ QVERIFY(!fakeFolder.currentLocalState().find("B/b1"));
+ QVERIFY(!fakeFolder.currentRemoteState().find("B/b1"));
+ QVERIFY(itemInstruction(completeSpy, "B/b1", CSYNC_INSTRUCTION_REMOVE));
+
+ QVERIFY(!fakeFolder.currentLocalState().find("B/b2"));
+ QVERIFY(!fakeFolder.currentRemoteState().find("B/b2"));
+ QVERIFY(isDehydrated("B/b3"));
+ QVERIFY(itemInstruction(completeSpy, "B/b2", CSYNC_INSTRUCTION_REMOVE));
+ QVERIFY(itemInstruction(completeSpy, "B/b3.nextcloud", CSYNC_INSTRUCTION_NEW));
+
+ QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->size, 25);
+ QVERIFY(itemInstruction(completeSpy, "C/c1", CSYNC_INSTRUCTION_SYNC));
+
+ QCOMPARE(fakeFolder.currentRemoteState().find("C/c2")->size, 26);
+ QVERIFY(itemInstruction(completeSpy, "C/c2", CSYNC_INSTRUCTION_CONFLICT));
+ cleanup();
+
+ auto expectedLocalState = fakeFolder.currentLocalState();
+ auto expectedRemoteState = fakeFolder.currentRemoteState();
+ QVERIFY(fakeFolder.syncOnce());
+ QCOMPARE(fakeFolder.currentLocalState(), expectedLocalState);
+ QCOMPARE(fakeFolder.currentRemoteState(), expectedRemoteState);
+ }
+
+ void testWipeVirtualSuffixFiles()
+ {
+ FakeFolder fakeFolder{ FileInfo{} };
+ fakeFolder.syncEngine().setSyncOptions(vfsSyncOptions());
+
+ // Create a suffix-vfs baseline
+
+ fakeFolder.remoteModifier().mkdir("A");
+ fakeFolder.remoteModifier().mkdir("A/B");
+ fakeFolder.remoteModifier().insert("f1");
+ fakeFolder.remoteModifier().insert("A/a1");
+ fakeFolder.remoteModifier().insert("A/a3");
+ fakeFolder.remoteModifier().insert("A/B/b1");
+ fakeFolder.localModifier().mkdir("A");
+ fakeFolder.localModifier().mkdir("A/B");
+ fakeFolder.localModifier().insert("f2");
+ fakeFolder.localModifier().insert("A/a2");
+ fakeFolder.localModifier().insert("A/B/b2");
+
+ QVERIFY(fakeFolder.syncOnce());
+
+ QVERIFY(fakeFolder.currentLocalState().find("f1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a3.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/B/b1.nextcloud"));
+
+ // Make local changes to a3
+ fakeFolder.localModifier().remove("A/a3.nextcloud");
+ fakeFolder.localModifier().insert("A/a3.nextcloud", 100);
+
+ // Now wipe the virtuals
+
+ SyncEngine::wipeVirtualFiles(fakeFolder.localPath(), fakeFolder.syncJournal(), fakeFolder.syncEngine().syncOptions()._vfs);
+
+ QVERIFY(!fakeFolder.currentLocalState().find("f1.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a3.nextcloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/B/b1.nextcloud"));
+
+ fakeFolder.syncEngine().setSyncOptions(SyncOptions{});
+ QVERIFY(fakeFolder.syncOnce());
+ QVERIFY(fakeFolder.currentRemoteState().find("A/a3.nextcloud")); // regular upload
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+ }
};
QTEST_GUILESS_MAIN(TestSyncVirtualFiles)