1
0
mirror of https://github.com/chylex/Nextcloud-Desktop.git synced 2025-04-25 01:15:48 +02:00

winvfs: initial work

Done by ckamm and dschmidt
This commit is contained in:
Christian Kamm 2018-08-15 10:46:16 +02:00 committed by Kevin Ottens
parent c3b1a872aa
commit 2b20985875
No known key found for this signature in database
GPG Key ID: 074BBBCB8DECC9E2
40 changed files with 1170 additions and 294 deletions

View File

@ -3,6 +3,11 @@ set(CMAKE_CXX_STANDARD 14)
project(client)
if(UNIT_TESTING)
include(CTest)
enable_testing()
endif()
set(BIN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
@ -52,6 +57,9 @@ include(Warnings)
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
@ -215,8 +223,9 @@ if( WIN32 )
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
@ -254,8 +263,6 @@ if(BUILD_SHELL_INTEGRATION)
endif()
if(UNIT_TESTING)
include(CTest)
enable_testing()
add_subdirectory(test)
endif(UNIT_TESTING)

View File

@ -31,13 +31,23 @@ HRESULT SetHKCRRegistryKeyAndValue(PCWSTR pszSubKey, PCWSTR pszValueName, PCWSTR
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);
}
@ -88,6 +98,11 @@ HRESULT NCContextMenuRegHandler::RegisterInprocServer(PCWSTR pszModule, const CL
{
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))
{

View File

@ -84,3 +84,4 @@ if(KRAZY2_EXECUTABLE)
${PROJECT_SOURCE_DIR}/src/cmd/*.cpp
)
endif()

View File

@ -9,4 +9,5 @@ set(common_SOURCES
${CMAKE_CURRENT_LIST_DIR}/syncjournalfilerecord.cpp
${CMAKE_CURRENT_LIST_DIR}/utility.cpp
${CMAKE_CURRENT_LIST_DIR}/remotepermissions.cpp
${CMAKE_CURRENT_LIST_DIR}/vfs.cpp
)

View File

@ -243,6 +243,12 @@ namespace Utility {
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

View File

@ -314,4 +314,24 @@ DWORD Utility::convertSizeToDWORD(size_t &convertVar)
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

58
src/common/vfs.cpp Normal file
View File

@ -0,0 +1,58 @@
/*
* 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;
}

97
src/common/vfs.h Normal file
View File

@ -0,0 +1,97 @@
/*
* 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 &params) = 0;
virtual void start(const VfsSetupParams &params) = 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

View File

@ -137,7 +137,8 @@ enum ItemType {
ItemTypeDirectory = 2,
ItemTypeSkip = 3,
ItemTypeVirtualFile = 4,
ItemTypeVirtualFileDownload = 5
ItemTypeVirtualFileDownload = 5,
ItemTypeVirtualFileDehydration = 6,
};
@ -146,7 +147,9 @@ enum ItemType {
// 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;
@ -177,7 +180,7 @@ struct OCSYNC_EXPORT csync_file_stat_t {
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)

View File

@ -25,7 +25,7 @@ struct csync_vio_handle_t;
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);

View File

@ -35,6 +35,7 @@
#include "csync_util.h"
#include "vio/csync_vio_local.h"
#include "common/vfsplugin.h"
#include <QtCore/QLoggingCategory>
#include <QtCore/QFile>
@ -73,7 +74,7 @@ int csync_vio_local_closedir(csync_vio_handle_t *dhandle) {
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;
@ -120,6 +121,11 @@ std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(csync_vio_handle_t *h
// 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;
}

View File

@ -38,6 +38,8 @@
#include <QtCore/QLoggingCategory>
#include "common/vfs.h"
Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "nextcloud.sync.csync.vio_local", QtInfoMsg)
/*
@ -113,7 +115,7 @@ static time_t FileTimeToUnixTime(FILETIME *filetime, DWORD *remainder)
}
}
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;
@ -137,12 +139,14 @@ std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(csync_vio_handle_t *h
}
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) {

View File

@ -535,7 +535,11 @@ void AccountSettings::slotFolderWizardAccepted()
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);

View File

@ -32,7 +32,8 @@
#include "filesystem.h"
#include "localdiscoverytracker.h"
#include "csync_exclude.h"
#include "common/vfs.h"
#include "plugin.h"
#include "creds/abstractcredentials.h"
#include <QTimer>
@ -42,6 +43,7 @@
#include <QMessageBox>
#include <QPushButton>
#include <QApplication>
static const char versionC[] = "version";
@ -115,10 +117,53 @@ Folder::Folder(const FolderDefinition &definition,
_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();
}
@ -218,12 +263,12 @@ QString Folder::cleanPath() const
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
@ -551,7 +596,8 @@ void Folder::downloadVirtualFile(const QString &_relativepath)
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();
@ -571,7 +617,7 @@ void Folder::saveToSettings() const
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.
@ -739,8 +785,7 @@ void Folder::setSyncOptions()
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()) {
@ -1046,6 +1091,30 @@ void Folder::slotWatcherUnreliable(const QString &message)
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()) {
@ -1076,6 +1145,11 @@ void Folder::registerFolderWatcher()
_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;
@ -1117,9 +1191,11 @@ void FolderDefinition::save(QSettings &settings, const FolderDefinition &folder)
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);
@ -1139,7 +1215,18 @@ bool FolderDefinition::load(QSettings &settings, const QString &alias,
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

View File

@ -21,6 +21,7 @@
#include "progressdispatcher.h"
#include "common/syncjournaldb.h"
#include "networkjobs.h"
#include "syncoptions.h"
#include <QObject>
#include <QStringList>
@ -33,6 +34,7 @@ class QSettings;
namespace OCC {
class Vfs;
class SyncEngine;
class AccountState;
class SyncRunFileLog;
@ -58,11 +60,15 @@ public:
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);
@ -173,7 +179,7 @@ public:
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();
@ -240,8 +246,8 @@ public:
*/
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:
@ -336,7 +342,19 @@ private slots:
/** 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();
@ -416,6 +434,11 @@ private:
* 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;
};
}

View File

@ -29,9 +29,6 @@
#ifdef Q_OS_MAC
#include <CoreServices/CoreServices.h>
#endif
#ifdef Q_OS_WIN
#include <shlobj.h>
#endif
#include <QMessageBox>
#include <QtCore>
@ -1003,7 +1000,6 @@ Folder *FolderMan::addFolder(AccountState *accountState, const FolderDefinition
}
_navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
return folder;
}

View File

@ -635,7 +635,10 @@ void OwncloudSetupWizard::slotAssistantFinished(int result)
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();

View File

@ -689,52 +689,51 @@ void SocketApi::command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListen
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)
auto data = FileData::get(file);
if (!data.folder)
continue;
if (file.endsWith(suffix))
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)
@ -1012,18 +1011,18 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
// 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()))

View File

@ -33,6 +33,7 @@ set(libsync_SRCS
networkjobs.cpp
owncloudpropagator.cpp
nextcloudtheme.cpp
plugin.cpp
progressdispatcher.cpp
propagatorjobs.cpp
propagatedownload.cpp
@ -140,3 +141,4 @@ else()
endif()
add_subdirectory(vfs)

View File

@ -77,7 +77,7 @@ bool DiscoveryPhase::isInSelectiveSyncBlackList(const QString &path) const
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.
@ -100,7 +100,7 @@ void DiscoveryPhase::checkSelectiveSyncNewFolder(const QString &path, RemotePerm
}
auto limit = _syncOptions._newBigFolderSizeLimit;
if (limit < 0 || _syncOptions._newFilesAreVirtual) {
if (limit < 0 || !_syncOptions._vfs) {
// no limit, everything is allowed;
return callback(false);
}

View File

@ -189,5 +189,15 @@ bool FileSystem::removeRecursively(const QString &path, const std::function<void
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

View File

@ -63,6 +63,11 @@ namespace FileSystem {
*/
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
*

View File

@ -584,11 +584,6 @@ QString OwncloudPropagator::getFilePath(const QString &tmp_file_name) const
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);

View File

@ -449,7 +449,6 @@ public:
/* 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.
*/

68
src/libsync/plugin.cpp Normal file
View File

@ -0,0 +1,68 @@
/*
* 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();
}
}

68
src/libsync/plugin.h Normal file
View File

@ -0,0 +1,68 @@
/*
* 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")

View File

@ -26,6 +26,7 @@
#include "common/asserts.h"
#include "clientsideencryptionjobs.h"
#include "propagatedownloadencrypted.h"
#include "common/vfs.h"
#include <QLoggingCategory>
#include <QNetworkAccessManager>
@ -68,13 +69,15 @@ QString OWNCLOUDSYNC_EXPORT createDownloadTmpFileName(const QString &previous)
}
// 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)
@ -86,7 +89,7 @@ GETFileJob::GETFileJob(AccountPtr account, const QString &path, QFile *device,
{
}
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)
@ -94,6 +97,8 @@ GETFileJob::GETFileJob(AccountPtr account, const QUrl &url, QFile *device,
, _device(device)
, _headers(headers)
, _expectedEtagForResume(expectedEtagForResume)
, _expectedContentLength(-1)
, _contentLength(0)
, _resumeStart(resumeStart)
, _errorStatus(SyncFileItem::NoStatus)
, _directDownloadUrl(url)
@ -201,6 +206,16 @@ void GETFileJob::slotMetaDataChanged()
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()) {
@ -399,17 +414,30 @@ void PropagateDownloadFile::startAfterIsEncryptedIsChecked()
{
_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;
}
@ -623,7 +651,7 @@ void PropagateDownloadFile::slotGetFinished()
// 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());
@ -957,6 +985,12 @@ void PropagateDownloadFile::downloadFinished()
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...
@ -968,15 +1002,20 @@ void PropagateDownloadFile::downloadFinished()
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);

View File

@ -30,10 +30,12 @@ class PropagateDownloadEncrypted;
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;
@ -50,11 +52,11 @@ class GETFileJob : public AbstractNetworkJob
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()
@ -101,6 +103,9 @@ public:
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();

View File

@ -90,8 +90,10 @@ void PropagateRemoteMove::start()
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());
@ -162,6 +164,7 @@ void PropagateRemoteMove::finalize()
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;

View File

@ -28,6 +28,7 @@
#include "common/asserts.h"
#include "configfile.h"
#include "discovery.h"
#include "common/vfs.h"
#ifdef Q_OS_WIN
#include <windows.h>
@ -332,6 +333,8 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
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 {
@ -469,10 +472,12 @@ void SyncEngine::startSync()
_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
@ -985,6 +990,30 @@ bool SyncEngine::shouldDiscoverLocally(const QString &path) const
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)

View File

@ -119,6 +119,13 @@ public:
/** 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:

View File

@ -15,6 +15,7 @@
#include "syncfileitem.h"
#include "common/syncjournalfilerecord.h"
#include "common/utility.h"
#include "filesystem.h"
#include <QLoggingCategory>
#include "csync/vio/csync_vio_local.h"
@ -37,17 +38,15 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri
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;

View File

@ -17,7 +17,7 @@
#include "owncloudlib.h"
#include <QString>
#include <chrono>
#include "common/vfs.h"
namespace OCC {
@ -37,8 +37,7 @@ struct SyncOptions
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

View File

@ -0,0 +1,12 @@
### 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()

View File

@ -0,0 +1,15 @@
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
)

View File

@ -0,0 +1,108 @@
/*
* 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

View File

@ -0,0 +1,62 @@
/*
* 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 &params) override;
void start(const VfsSetupParams &params) 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

View File

@ -21,7 +21,12 @@ macro(nextcloud_add_test test_class additional_cpp)
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)

View File

@ -931,6 +931,7 @@ public:
syncOnce();
}
OCC::AccountPtr account() const { return _account; }
OCC::SyncEngine &syncEngine() const { return *_syncEngine; }
OCC::SyncJournalDb &syncJournal() const { return *_journalDb; }

View File

@ -7,6 +7,8 @@
#include <QtTest>
#include "syncenginetestutils.h"
#include "common/vfs.h"
#include "plugin.h"
#include <syncengine.h>
using namespace OCC;
@ -38,7 +40,7 @@ void triggerDownload(FakeFolder &folder, const QByteArray &path)
{
auto &journal = folder.syncJournal();
SyncJournalFileRecord record;
journal.getFileRecord(path + ".owncloud", &record);
journal.getFileRecord(path + ".nextcloud", &record);
if (!record.isValid())
return;
record._type = ItemTypeVirtualFileDownload;
@ -46,6 +48,25 @@ void triggerDownload(FakeFolder &folder, const QByteArray &path)
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
@ -64,9 +85,7 @@ private slots:
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 &)));
@ -84,20 +103,20 @@ private slots:
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();
@ -105,10 +124,10 @@ private slots:
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();
@ -116,24 +135,24 @@ private slots:
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
@ -141,55 +160,53 @@ private slots:
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 &)));
@ -208,8 +225,8 @@ private slots:
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
@ -219,8 +236,8 @@ private slots:
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());
@ -233,11 +250,11 @@ private slots:
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);
@ -248,11 +265,11 @@ private slots:
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();
}
@ -260,9 +277,7 @@ private slots:
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 &)));
@ -288,19 +303,17 @@ private slots:
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 &)));
@ -318,12 +331,12 @@ private slots:
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
@ -338,17 +351,17 @@ private slots:
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);
@ -357,20 +370,18 @@ private slots:
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 &)));
@ -384,7 +395,7 @@ private slots:
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
@ -392,29 +403,28 @@ private slots:
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());
@ -422,39 +432,29 @@ private slots:
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");
@ -469,31 +469,27 @@ private slots:
// 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
@ -512,14 +508,14 @@ private slots:
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"));
@ -535,14 +531,14 @@ private slots:
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"));
@ -556,21 +552,21 @@ private slots:
// 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"));
@ -590,9 +586,7 @@ private slots:
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 &)));
@ -601,28 +595,28 @@ private slots:
};
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();
}
@ -630,9 +624,7 @@ private slots:
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 &)));
@ -645,30 +637,153 @@ private slots:
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)