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:
parent
c3b1a872aa
commit
2b20985875
@ -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)
|
||||
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -84,3 +84,4 @@ if(KRAZY2_EXECUTABLE)
|
||||
${PROJECT_SOURCE_DIR}/src/cmd/*.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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
58
src/common/vfs.cpp
Normal 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
97
src/common/vfs.h
Normal 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 ¶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
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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()))
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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
68
src/libsync/plugin.cpp
Normal 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
68
src/libsync/plugin.h
Normal 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")
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
12
src/libsync/vfs/CMakeLists.txt
Normal file
12
src/libsync/vfs/CMakeLists.txt
Normal 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()
|
15
src/libsync/vfs/suffix/CMakeLists.txt
Normal file
15
src/libsync/vfs/suffix/CMakeLists.txt
Normal 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
|
||||
)
|
||||
|
108
src/libsync/vfs/suffix/vfs_suffix.cpp
Normal file
108
src/libsync/vfs/suffix/vfs_suffix.cpp
Normal 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
|
62
src/libsync/vfs/suffix/vfs_suffix.h
Normal file
62
src/libsync/vfs/suffix/vfs_suffix.h
Normal 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 ¶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
|
@ -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)
|
||||
|
@ -931,6 +931,7 @@ public:
|
||||
syncOnce();
|
||||
}
|
||||
|
||||
OCC::AccountPtr account() const { return _account; }
|
||||
OCC::SyncEngine &syncEngine() const { return *_syncEngine; }
|
||||
OCC::SyncJournalDb &syncJournal() const { return *_journalDb; }
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user