Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/gui/folder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,6 @@ void Folder::startVfs()
OC_ENFORCE(_vfs->mode() == _definition.virtualFilesMode);

VfsSetupParams vfsParams(_accountState->account(), webDavUrl(), _definition.spaceId(), displayName(), _engine.get());
vfsParams.filesystemPath = path();
vfsParams.journal = &_journal;
vfsParams.providerDisplayName = Theme::instance()->appNameGUI();
vfsParams.providerName = Theme::instance()->appName();
Expand Down
2 changes: 2 additions & 0 deletions src/libsync/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ add_library(libsync SHARED
abstractcorejob.cpp

appprovider.cpp

path.cpp
)

if(WIN32)
Expand Down
11 changes: 6 additions & 5 deletions src/libsync/common/filesystembase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ namespace OCC {

Q_LOGGING_CATEGORY(lcFileSystem, "sync.filesystem", QtInfoMsg)

std::filesystem::path FileSystem::toFilesystemPath(QString path)
std::filesystem::path FileSystem::toFilesystemPath(const QString &path)
{
#ifdef Q_OS_WIN
path = FileSystem::longWinPath(path);
return QtPrivate::toFilesystemPath(FileSystem::longWinPath(path));
#else
return QtPrivate::toFilesystemPath(path);
#endif
return std::filesystem::path(reinterpret_cast<const char16_t *>(path.cbegin()), reinterpret_cast<const char16_t *>(path.cend()));
}

QString FileSystem::fromFilesystemPath(const std::filesystem::path &path)
Expand All @@ -71,9 +72,9 @@ QString FileSystem::fromFilesystemPath(const std::filesystem::path &path)
return QDir::fromNativeSeparators(QString::fromWCharArray(view.data(), view.length()));
#elif defined(Q_OS_MACOS)
// based on QFile::decodeName
return QString::fromStdString(path.native()).normalized(QString::NormalizationForm_C);
return QtPrivate::fromFilesystemPath(path).normalized(QString::NormalizationForm_C);
#else
return QString::fromStdString(path.native());
return QtPrivate::fromFilesystemPath(path);
#endif
}

Expand Down
4 changes: 2 additions & 2 deletions src/libsync/common/filesystembase.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
#include <QString>

#include <cstdint>
#include <ctime>


class QFile;
Expand All @@ -48,9 +47,10 @@ OPENCLOUD_SYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcFileSystem)
{
OPENCLOUD_SYNC_EXPORT Q_NAMESPACE;

OPENCLOUD_SYNC_EXPORT std::filesystem::path toFilesystemPath(QString path);
OPENCLOUD_SYNC_EXPORT std::filesystem::path toFilesystemPath(const QString &path);
OPENCLOUD_SYNC_EXPORT QString fromFilesystemPath(const std::filesystem::path &path);


/**
* List of characters not allowd in filenames on Windows
*/
Expand Down
31 changes: 31 additions & 0 deletions src/libsync/path.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2025 Hannah von Reth <h.vonreth@opencloud.eu>

#include "path.h"

#include "libsync/common/filesystembase.h"

OCC::FileSystem::Path::Path(QAnyStringView path)
: _path(toFilesystemPath(path.toString()))
{
}

OCC::FileSystem::Path::Path(const std::filesystem::path &path)
: _path(path)
{
}

OCC::FileSystem::Path::Path(std::filesystem::path &&path)
: _path(std::move(path))
{
}

OCC::FileSystem::Path OCC::FileSystem::Path::relative(QAnyStringView path)
{
return QtPrivate::toFilesystemPath(path.toString());
}

QString OCC::FileSystem::Path::toString() const
{
return fromFilesystemPath(_path);
}
Comment on lines +8 to +31
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileSystem::Path is a new cross-platform path abstraction with Windows-specific behavior (long-path/\\?\ handling) and several operator overloads (relative, /, toString). There don’t appear to be tests covering its conversion/join behavior yet; adding a small unit test (including a Windows-only section) would help prevent regressions in path normalization and string round-tripping.

Copilot uses AI. Check for mistakes.
68 changes: 68 additions & 0 deletions src/libsync/path.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2025 Hannah von Reth <h.vonreth@opencloud.eu>

#pragma once

#include "libsync/opencloudsynclib.h"

#include <QString>

#include <filesystem>

namespace OCC {
namespace FileSystem {
class OPENCLOUD_SYNC_EXPORT Path
{
public:
/**
* Will create an absolute path from the given QString.
* On Windows it will create a UNC path starting with "\\?\".
* For path segments use Path::relative.
* @param path
*/
explicit Path(QAnyStringView path);

Path(const std::filesystem::path &path);

Path(std::filesystem::path &&path);

/**
* Creates a relative path segment
* @param path
* @return A relative path
*/
static Path relative(QAnyStringView path);

const std::filesystem::path &get() const { return _path; }

[[nodiscard]] operator std::filesystem::path() const { return _path; }

explicit operator QString() const { return toString(); }

Path operator/(const Path &other) const { return _path / other._path; }
Path operator/(const std::filesystem::path &other) const { return _path / other; }
Path operator/(QAnyStringView other) const { return _path / relative(other); }

Path &operator/=(const Path &other)
{
_path /= other._path;
return *this;
}
Path &operator/=(const std::filesystem::path &other)
{
_path /= other;
return *this;
}
Path &operator/=(QAnyStringView other)
{
_path /= relative(other);
return *this;
}

QString toString() const;

private:
std::filesystem::path _path;
};
}
}
15 changes: 14 additions & 1 deletion src/libsync/vfs/vfs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "libsync/common/syncjournaldb.h"
#include "libsync/common/version.h"
#include "libsync/filesystem.h"
#include "libsync/syncengine.h"

#include <QApplication>
#include <QCoreApplication>
Expand Down Expand Up @@ -100,7 +101,7 @@ void Vfs::wipeDehydratedVirtualFiles()

// 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.
const QString absolutePath = _setupParams->filesystemPath + relativePath;
const auto absolutePath = QString(_setupParams->root() / relativePath);
if (QFile::exists(absolutePath)) {
// according to our db this is a dehydrated file, check it to be sure
if (isDehydratedPlaceholder(absolutePath)) {
Expand Down Expand Up @@ -261,7 +262,9 @@ VfsSetupParams::VfsSetupParams(const AccountPtr &account, const QUrl &baseUrl, c
, _syncEngine(syncEngine)
, _spaceId(spaceId)
, _folderDisplayName(folderDisplayName)
, _root(syncEngine->localPath())
{
Q_ASSERT(filesystemPath().endsWith('/'_L1));
}

QString VfsSetupParams::folderDisplayName() const
Expand All @@ -273,3 +276,13 @@ SyncEngine *VfsSetupParams::syncEngine() const
{
return _syncEngine;
}

QString VfsSetupParams::filesystemPath() const
{
return _root.toString();
}

const FileSystem::Path &VfsSetupParams::root() const
{
return _root;
}
22 changes: 11 additions & 11 deletions src/libsync/vfs/vfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@
*/
#pragma once

#include "../common/pinstate.h"
#include "../common/result.h"
#include "../common/syncfilestatus.h"
#include "../common/utility.h"
#include "assert.h"
#include "filesystem.h"
#include "libsync/accountfwd.h"
#include "libsync/common/pinstate.h"
#include "libsync/common/result.h"
#include "libsync/common/syncfilestatus.h"
#include "libsync/common/utility.h"
#include "libsync/discoveryinfo.h"
#include "libsync/opencloudsynclib.h"
#include "libsync/path.h"

#include <QObject>
#include <QSharedPointer>
#include <QUrl>
#include <QVersionNumber>

#include <QFuture>
#include <filesystem>
#include <memory>

Expand All @@ -43,11 +43,7 @@ class HydrationJob;
struct OPENCLOUD_SYNC_EXPORT VfsSetupParams
{
explicit VfsSetupParams(const AccountPtr &account, const QUrl &baseUrl, const QString &spaceId, const QString &folderDisplayName, SyncEngine *syncEngine);
/** The full path to the folder on the local filesystem
*
* Always ends with /.
*/
QString filesystemPath;

QString folderDisplayName() const;

/// Account url, credentials etc for network calls
Expand All @@ -69,11 +65,15 @@ struct OPENCLOUD_SYNC_EXPORT VfsSetupParams

SyncEngine *syncEngine() const;

QString filesystemPath() const;
const FileSystem::Path &root() const;

private:
QUrl _baseUrl;
SyncEngine *_syncEngine;
QString _spaceId;
QString _folderDisplayName;
FileSystem::Path _root;
};

/** Interface describing how to deal with virtual/placeholder files.
Expand Down
10 changes: 5 additions & 5 deletions src/plugins/vfs/cfapi/cfapiwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ void CALLBACK cfApiRename(const CF_CALLBACK_INFO *callbackInfo, const CF_CALLBAC
if (callbackParameters->Rename.Flags & (CF_CALLBACK_RENAME_FLAG_TARGET_IN_SCOPE | CF_CALLBACK_RENAME_FLAG_SOURCE_IN_SCOPE) &&
// CF_CALLBACK_RENAME_FLAG_TARGET_IN_SCOPE is also set for any other sync root we manage
// but in that case windows will hydrate the file before its moved
OCC::FileSystem::isChildPathOf(target, context.vfs->params().filesystemPath) && context.vfs->isDehydratedPlaceholder(context.path)
OCC::FileSystem::isChildPathOf(target, context.vfs->params().filesystemPath()) && context.vfs->isDehydratedPlaceholder(context.path)
&& context.vfs->params().syncEngine()->isExcluded(qtPath)) {
reject = true;
}
Expand Down Expand Up @@ -356,7 +356,7 @@ QString createSyncRootID(const QString &providerName, const QUuid &accountUUID,

void OCC::CfApiWrapper::registerSyncRoot(const VfsSetupParams &params, const std::function<void(QString)> &callback)
{
const auto nativePath = QDir::toNativeSeparators(params.filesystemPath);
const auto nativePath = QDir::toNativeSeparators(params.filesystemPath());
winrt::StorageFolder::GetFolderFromPathAsync(reinterpret_cast<const wchar_t *>(nativePath.utf16()))
.Completed([params, callback](const winrt::IAsyncOperation<winrt::StorageFolder> &result, winrt::AsyncStatus status) {
if (status != winrt::AsyncStatus::Completed) {
Expand All @@ -365,7 +365,7 @@ void OCC::CfApiWrapper::registerSyncRoot(const VfsSetupParams &params, const std
}
try {
const auto iconPath = QCoreApplication::applicationFilePath();
const auto id = createSyncRootID(params.providerName, params.account->uuid(), params.filesystemPath);
const auto id = createSyncRootID(params.providerName, params.account->uuid(), params.filesystemPath());
const auto version = params.providerVersion.toString();

winrt::StorageProviderSyncRootInfo info;
Expand Down Expand Up @@ -400,7 +400,7 @@ void OCC::CfApiWrapper::registerSyncRoot(const VfsSetupParams &params, const std
}
callback({});
} catch (const winrt::hresult_error &ex) {
callback(u"Failed to register sync root %1: %2"_s.arg(params.filesystemPath, Utility::formatWinError(ex.code())));
callback(u"Failed to register sync root %1: %2"_s.arg(params.filesystemPath(), Utility::formatWinError(ex.code())));
}
});
}
Expand Down Expand Up @@ -431,7 +431,7 @@ OCC::Result<void, QString> OCC::CfApiWrapper::unregisterSyncRoot(const VfsSetupP
try {
std::lock_guard lock(sRegister_mutex);
winrt::StorageProviderSyncRootManager::Unregister(
reinterpret_cast<const wchar_t *>(createSyncRootID(params.providerName, params.account->uuid(), params.filesystemPath).utf16()));
reinterpret_cast<const wchar_t *>(createSyncRootID(params.providerName, params.account->uuid(), params.filesystemPath()).utf16()));
} catch (winrt::hresult_error const &ex) {
return u"unregisterSyncRoot failed: %1"_s.arg(Utility::formatWinError(ex.code()));
}
Expand Down
15 changes: 7 additions & 8 deletions src/plugins/vfs/cfapi/vfs_cfapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ void VfsCfApi::startImpl(const VfsSetupParams &params)

cfapi::registerSyncRoot(params, [this](const QString &errorMessage) {
if (errorMessage.isEmpty()) {
auto connectResult = cfapi::connectSyncRoot(this->params().filesystemPath, this);
auto connectResult = cfapi::connectSyncRoot(this->params().filesystemPath(), this);
if (!connectResult) {
qCCritical(lcCfApi) << u"Initialization failed, couldn't connect sync root:" << connectResult.error();
return;
Expand Down Expand Up @@ -155,7 +155,7 @@ void VfsCfApi::stop()
if (_connectionKey.Internal != 0) {
const auto result = cfapi::disconnectSyncRoot(std::move(_connectionKey));
if (!result) {
qCCritical(lcCfApi) << u"Disconnect failed for" << params().filesystemPath << u":" << result.error();
qCCritical(lcCfApi) << u"Disconnect failed for" << params().filesystemPath() << u":" << result.error();
}
}
}
Expand All @@ -164,7 +164,7 @@ void VfsCfApi::unregisterFolder()
{
const auto result = cfapi::unregisterSyncRoot(params());
if (!result) {
qCCritical(lcCfApi) << u"Unregistration failed for" << params().filesystemPath << u":" << result.error();
qCCritical(lcCfApi) << u"Unregistration failed for" << params().filesystemPath() << u":" << result.error();
}

#if 0
Expand Down Expand Up @@ -197,15 +197,14 @@ Result<Vfs::ConvertToPlaceholderResult, QString> VfsCfApi::updateMetadata(const

Result<void, QString> VfsCfApi::createPlaceholder(const SyncFileItem &item)
{
Q_ASSERT(params().filesystemPath.endsWith('/'_L1));
const auto localPath = QDir::toNativeSeparators(params().filesystemPath + item.localName());
const auto localPath = QDir::toNativeSeparators(params().filesystemPath() + item.localName());
const auto result = cfapi::createPlaceholderInfo(localPath, item._modtime, item._size, item._fileId);
return result;
}

bool VfsCfApi::needsMetadataUpdate(const SyncFileItem &item)
{
const QString path = params().filesystemPath + item.localName();
const QString path = params().filesystemPath() + item.localName();
if (!QFileInfo::exists(path)) {
return false;
}
Expand Down Expand Up @@ -258,13 +257,13 @@ bool VfsCfApi::setPinState(const QString &folderPath, PinState state)
{
qCDebug(lcCfApi) << u"setPinState" << folderPath << state;

const auto localPath = QDir::toNativeSeparators(params().filesystemPath + folderPath);
const auto localPath = QDir::toNativeSeparators(params().filesystemPath() + folderPath);
return static_cast<bool>(cfapi::setPinState(localPath, state, cfapi::Recurse));
}

Optional<PinState> VfsCfApi::pinState(const QString &folderPath)
{
const auto localPath = QDir::toNativeSeparators(params().filesystemPath + folderPath);
const auto localPath = QDir::toNativeSeparators(params().filesystemPath() + folderPath);
const auto info = cfapi::findPlaceholderInfo<CF_PLACEHOLDER_BASIC_INFO>(localPath);
if (!info) {
qCDebug(lcCfApi) << u"Couldn't find pin state for regular non-placeholder file" << localPath;
Expand Down
1 change: 0 additions & 1 deletion test/testutils/syncenginetestutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,6 @@ void FakeFolder::switchToVfs(QSharedPointer<OCC::Vfs> vfs)
_syncEngine->setSyncOptions(opts);

OCC::VfsSetupParams vfsParams(account(), OCC::TestUtils::dummyDavUrl(), QString(), u"DisplayName"_s, &syncEngine());
vfsParams.filesystemPath = localPath();
vfsParams.journal = _journalDb.get();
vfsParams.providerName = QStringLiteral("OC-TEST");
vfsParams.providerDisplayName = QStringLiteral("OC-TEST");
Expand Down