From 2b9427e9dfd7e85dd4b789b4dd74b582f75e21d2 Mon Sep 17 00:00:00 2001 From: Hannah von Reth Date: Mon, 2 Mar 2026 17:48:36 +0100 Subject: [PATCH 1/6] Path add QDebug << --- src/libsync/path.cpp | 5 +++++ src/libsync/path.h | 3 +++ src/plugins/vfs/cfapi/vfs_cfapi.cpp | 12 ++++++------ src/plugins/vfs/cfapi/vfs_cfapi.h | 2 +- test/testutility.cpp | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/libsync/path.cpp b/src/libsync/path.cpp index bdb57b6bc4..8f0916f57b 100644 --- a/src/libsync/path.cpp +++ b/src/libsync/path.cpp @@ -46,3 +46,8 @@ bool OCC::FileSystem::Path::exists() const } return exists; } + +QDebug operator<<(QDebug debug, const OCC::FileSystem::Path &path) +{ + return debug << u"Path(" << path.toString() << u")"; +} diff --git a/src/libsync/path.h b/src/libsync/path.h index 5d53cfe861..c52dd2c2f6 100644 --- a/src/libsync/path.h +++ b/src/libsync/path.h @@ -74,3 +74,6 @@ namespace FileSystem { }; } } + + +OPENCLOUD_SYNC_EXPORT QDebug operator<<(QDebug debug, const OCC::FileSystem::Path &path); diff --git a/src/plugins/vfs/cfapi/vfs_cfapi.cpp b/src/plugins/vfs/cfapi/vfs_cfapi.cpp index 666fc410e2..ea9b743ea8 100644 --- a/src/plugins/vfs/cfapi/vfs_cfapi.cpp +++ b/src/plugins/vfs/cfapi/vfs_cfapi.cpp @@ -213,20 +213,20 @@ bool VfsCfApi::needsMetadataUpdate(const SyncFileItem &item) bool VfsCfApi::isDehydratedPlaceholder(const QString &filePath) { - const auto path = QDir::toNativeSeparators(filePath); - return cfapi::isSparseFile(path); + return cfapi::isDehydratedPlaceholder(FileSystem::Path(filePath)); } -LocalInfo VfsCfApi::statTypeVirtualFile(const std::filesystem::directory_entry &path, ItemType type) +LocalInfo VfsCfApi::statTypeVirtualFile(const std::filesystem::directory_entry &entry, ItemType type) { // only get placeholder info if it's a file if (type == ItemTypeFile) { - if (auto placeholderInfo = cfapi::findPlaceholderInfo(FileSystem::fromFilesystemPath(path))) { + const auto path = FileSystem::Path(entry); + if (auto placeholderInfo = cfapi::findPlaceholderInfo(path.toString())) { Q_ASSERT(placeholderInfo.handle()); FILE_ATTRIBUTE_TAG_INFO attributeInfo = {}; if (!GetFileInformationByHandleEx(placeholderInfo.handle(), FileAttributeTagInfo, &attributeInfo, sizeof(attributeInfo))) { const auto error = GetLastError(); - qCCritical(lcCfApi) << u"GetFileInformationByHandle failed on" << path.path() << OCC::Utility::formatWinError(error); + qCCritical(lcCfApi) << u"GetFileInformationByHandle failed on" << path << OCC::Utility::formatWinError(error); return {}; } const CF_PLACEHOLDER_STATE placeholderState = CfGetPlaceholderStateFromAttributeTag(attributeInfo.FileAttributes, attributeInfo.ReparseTag); @@ -250,7 +250,7 @@ LocalInfo VfsCfApi::statTypeVirtualFile(const std::filesystem::directory_entry & } } } - return LocalInfo(path, type); + return LocalInfo(entry, type); } bool VfsCfApi::setPinState(const QString &folderPath, PinState state) diff --git a/src/plugins/vfs/cfapi/vfs_cfapi.h b/src/plugins/vfs/cfapi/vfs_cfapi.h index 80962318ba..161443f027 100644 --- a/src/plugins/vfs/cfapi/vfs_cfapi.h +++ b/src/plugins/vfs/cfapi/vfs_cfapi.h @@ -58,7 +58,7 @@ class VfsCfApi : public Vfs void cancelHydration(const OCC::CfApiWrapper::CallBackContext &context); - LocalInfo statTypeVirtualFile(const std::filesystem::directory_entry &path, ItemType type) override; + LocalInfo statTypeVirtualFile(const std::filesystem::directory_entry &entry, ItemType type) override; public Q_SLOTS: void fileStatusChanged(const QString &systemFileName, OCC::SyncFileStatus fileStatus) override; diff --git a/test/testutility.cpp b/test/testutility.cpp index e4beeed04c..945441dfe2 100644 --- a/test/testutility.cpp +++ b/test/testutility.cpp @@ -383,7 +383,7 @@ private Q_SLOTS: if (SUCCEEDED(hres)) { hres = ppf->Save(target.native().data(), true); if (SUCCEEDED(hres)) { - qDebug() << u"Created lnk" << target << u"->" << path; + qDebug() << u"Created lnk" << target.native() << u"->" << path; } else { qCritical() << u"Failed to create lnk: Save" << OCC::Utility::formatWinError(hres); } From 834d316b62834fa25c1abcbd52bdbcf2675e0a11 Mon Sep 17 00:00:00 2001 From: Hannah von Reth Date: Mon, 2 Mar 2026 17:50:58 +0100 Subject: [PATCH 2/6] Wehether or not a file is a dehydrated placholed, depends on more than FILE_ATTRIBUTE_SPARSE_FILE --- src/plugins/vfs/cfapi/cfapiwrapper.cpp | 23 +++++++++++++++++------ src/plugins/vfs/cfapi/cfapiwrapper.h | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/plugins/vfs/cfapi/cfapiwrapper.cpp b/src/plugins/vfs/cfapi/cfapiwrapper.cpp index 5a89ad0c92..6bccc18cfe 100644 --- a/src/plugins/vfs/cfapi/cfapiwrapper.cpp +++ b/src/plugins/vfs/cfapi/cfapiwrapper.cpp @@ -476,13 +476,24 @@ OCC::Result OCC::CfApiWrapper::disconnectSyncRoot(CF_CONNECTION_K return {}; } } - - -bool OCC::CfApiWrapper::isSparseFile(const QString &path) +bool OCC::CfApiWrapper::isDehydratedPlaceholder(const FileSystem::Path &path) { - const auto p = path.toStdWString(); - const auto attributes = GetFileAttributes(p.data()); - return (attributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0; + const auto handle = OCC::Utility::Handle::createHandle(path); + if (!handle) { + qCWarning(lcCfApiWrapper) << u"Failed to get file handle" << path << handle.errorMessage(); + return false; + } + FILE_ATTRIBUTE_TAG_INFO targInfo = {}; + if (!GetFileInformationByHandleEx(handle, FileAttributeTagInfo, &targInfo, sizeof(targInfo))) { + const auto error = GetLastError(); + qCWarning(lcCfApiWrapper) << u"Failed to get file attribute tag info for" << path << OCC::Utility::formatWinError(error); + return false; + } + const CF_PLACEHOLDER_STATE state = CfGetPlaceholderStateFromAttributeTag(targInfo.FileAttributes, targInfo.ReparseTag); + if (state == CF_PLACEHOLDER_STATE_NO_STATES) { + return false; + } + return state & CF_PLACEHOLDER_STATE_PARTIAL; } template <> diff --git a/src/plugins/vfs/cfapi/cfapiwrapper.h b/src/plugins/vfs/cfapi/cfapiwrapper.h index b3e49fd6c5..494574ee0b 100644 --- a/src/plugins/vfs/cfapi/cfapiwrapper.h +++ b/src/plugins/vfs/cfapi/cfapiwrapper.h @@ -98,7 +98,7 @@ namespace CfApiWrapper { Result connectSyncRoot(const QString &path, VfsCfApi *context); Result disconnectSyncRoot(CF_CONNECTION_KEY &&key); - bool isSparseFile(const QString &path); + bool isDehydratedPlaceholder(const FileSystem::Path &path); /** * The placeholder info can have a dynamic size, by default we don't query FileIdentity From 77cba577945dd31616b953b17343f80433d6079b Mon Sep 17 00:00:00 2001 From: Hannah von Reth Date: Mon, 2 Mar 2026 17:51:45 +0100 Subject: [PATCH 3/6] We always dehydrate the whole file --- src/plugins/vfs/cfapi/cfapiwrapper.cpp | 9 ++------- src/plugins/vfs/cfapi/cfapiwrapper.h | 2 +- src/plugins/vfs/cfapi/vfs_cfapi.cpp | 5 ++++- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/plugins/vfs/cfapi/cfapiwrapper.cpp b/src/plugins/vfs/cfapi/cfapiwrapper.cpp index 6bccc18cfe..1afdb55544 100644 --- a/src/plugins/vfs/cfapi/cfapiwrapper.cpp +++ b/src/plugins/vfs/cfapi/cfapiwrapper.cpp @@ -586,17 +586,12 @@ OCC::Result OCC::CfApiWrapper::up return updatePlaceholderState(path, modtime, size, fileId, replacesPath); } -OCC::Result OCC::CfApiWrapper::dehydratePlaceholder(const QString &path, qint64 size, const QByteArray &fileId) +OCC::Result OCC::CfApiWrapper::dehydratePlaceholder(const QString &path, const QByteArray &fileId) { const auto info = findPlaceholderInfo(path); if (info) { - setPinState(path, OCC::PinState::OnlineOnly, OCC::CfApiWrapper::NoRecurse); - - CF_FILE_RANGE dehydrationRange = {}; - dehydrationRange.Length.QuadPart = size; - const qint64 result = CfUpdatePlaceholder(Utility::Handle::createHandle(OCC::FileSystem::toFilesystemPath(path)), nullptr, fileId.data(), - static_cast(fileId.size()), &dehydrationRange, 1, CF_UPDATE_FLAG_MARK_IN_SYNC | CF_UPDATE_FLAG_DEHYDRATE, nullptr, nullptr); + static_cast(fileId.size()), nullptr, 0, CF_UPDATE_FLAG_MARK_IN_SYNC | CF_UPDATE_FLAG_DEHYDRATE, nullptr, nullptr); if (result != S_OK) { const auto errorMessage = createErrorMessageForPlaceholderUpdateAndCreate(path, u"Couldn't update placeholder info"_s); qCWarning(lcCfApiWrapper) << errorMessage << path << u":" << OCC::Utility::formatWinError(result); diff --git a/src/plugins/vfs/cfapi/cfapiwrapper.h b/src/plugins/vfs/cfapi/cfapiwrapper.h index 494574ee0b..df68148d99 100644 --- a/src/plugins/vfs/cfapi/cfapiwrapper.h +++ b/src/plugins/vfs/cfapi/cfapiwrapper.h @@ -123,7 +123,7 @@ namespace CfApiWrapper { const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath = QString()); Result convertToPlaceholder( const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath); - Result dehydratePlaceholder(const QString &path, qint64 size, const QByteArray &fileId); + Result dehydratePlaceholder(const QString &path, const QByteArray &fileId); Result updatePlaceholderMarkInSync(const Utility::Handle &handle); bool isPlaceHolderInSync(const QString &filePath); } diff --git a/src/plugins/vfs/cfapi/vfs_cfapi.cpp b/src/plugins/vfs/cfapi/vfs_cfapi.cpp index ea9b743ea8..706818e62e 100644 --- a/src/plugins/vfs/cfapi/vfs_cfapi.cpp +++ b/src/plugins/vfs/cfapi/vfs_cfapi.cpp @@ -185,7 +185,10 @@ Result VfsCfApi::updateMetadata(const const auto replacesPath = QDir::toNativeSeparators(replacesFile); if (syncItem._type == ItemTypeVirtualFileDehydration) { - return cfapi::dehydratePlaceholder(localPath, syncItem._size, syncItem._fileId); + auto result = cfapi::dehydratePlaceholder(localPath, syncItem._fileId); + // if the dehydration call succeeded, check whether the placeholder is dehydrated + Q_ASSERT(!result || isDehydratedPlaceholder(filePath)); + return result; } else { if (cfapi::findPlaceholderInfo(localPath)) { return cfapi::updatePlaceholderInfo(localPath, syncItem._modtime, syncItem._size, syncItem._fileId, replacesPath); From 0f99002c9121ed9fc1441b35d9754143a4c71e8a Mon Sep 17 00:00:00 2001 From: Hannah von Reth Date: Mon, 2 Mar 2026 17:52:26 +0100 Subject: [PATCH 4/6] Always set pin state --- src/plugins/vfs/cfapi/cfapiwrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/vfs/cfapi/cfapiwrapper.cpp b/src/plugins/vfs/cfapi/cfapiwrapper.cpp index 1afdb55544..98869e8dbb 100644 --- a/src/plugins/vfs/cfapi/cfapiwrapper.cpp +++ b/src/plugins/vfs/cfapi/cfapiwrapper.cpp @@ -606,8 +606,8 @@ OCC::Result OCC::CfApiWrapper::de qCWarning(lcCfApiWrapper) << errorMessage << path << u":" << OCC::Utility::formatWinError(result); return errorMessage; } + setPinState(path, OCC::PinState::OnlineOnly, OCC::CfApiWrapper::NoRecurse); } - return OCC::Vfs::ConvertToPlaceholderResult::Ok; } From 485481377c253b52801e51331afe82dc8d98c958 Mon Sep 17 00:00:00 2001 From: Hannah von Reth Date: Mon, 2 Mar 2026 17:52:39 +0100 Subject: [PATCH 5/6] Fix warning --- src/plugins/vfs/cfapi/vfs_cfapi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/vfs/cfapi/vfs_cfapi.cpp b/src/plugins/vfs/cfapi/vfs_cfapi.cpp index 706818e62e..5c1dae9963 100644 --- a/src/plugins/vfs/cfapi/vfs_cfapi.cpp +++ b/src/plugins/vfs/cfapi/vfs_cfapi.cpp @@ -134,7 +134,7 @@ void VfsCfApi::startImpl(const VfsSetupParams ¶ms) }); } -Result CfApiVfsPluginFactory::prepare(const QString &path, const QUuid &accountUuid) const +Result CfApiVfsPluginFactory::prepare(const QString &path, const QUuid &) const { if (QDir(path).isRoot()) { return tr("The Virtual filesystem feature does not support a drive as sync root"); From ebe8252772941486b42c858297ff6264e1c06c78 Mon Sep 17 00:00:00 2001 From: Hannah von Reth Date: Mon, 9 Mar 2026 15:28:16 +0100 Subject: [PATCH 6/6] Don't rely on QDebug overload for std::path --- src/plugins/vfs/xattr/vfs_xattr.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/vfs/xattr/vfs_xattr.cpp b/src/plugins/vfs/xattr/vfs_xattr.cpp index eab625bf57..f6496419d7 100644 --- a/src/plugins/vfs/xattr/vfs_xattr.cpp +++ b/src/plugins/vfs/xattr/vfs_xattr.cpp @@ -66,7 +66,7 @@ OpenVfsAttributes::PlaceHolderAttributes placeHolderAttributes(const std::filesy { const auto data = OCC::FileSystem::Xattr::getxattr(path, QString::fromUtf8(OpenVfsConstants::XAttributeNames::Data)); if (!data) { - qCWarning(lcVfsXAttr) << "No OpenVFS xattr found for" << path; + qCWarning(lcVfsXAttr) << u"No OpenVFS xattr found for" << path.native(); } return OpenVfsAttributes::PlaceHolderAttributes::fromData(path, data ? std::vector{data->cbegin(), data->cend()} : std::vector{}); } @@ -388,7 +388,7 @@ void VfsXAttr::slotHydrateJobFinished() const auto targetPath = FileSystem::toFilesystemPath(hydration->targetFileName()); Q_ASSERT(!targetPath.empty()); - qCInfo(lcVfsXAttr) << u"Hydration Job finished for" << targetPath; + qCInfo(lcVfsXAttr) << u"Hydration Job finished for" << targetPath.native(); if (std::filesystem::exists(targetPath)) { auto item = OCC::SyncFileItem::fromSyncJournalFileRecord(hydration->record()); @@ -398,17 +398,17 @@ void VfsXAttr::slotHydrateJobFinished() if (auto inode = FileSystem::getInode(targetPath)) { item->_inode = inode.value(); } else { - qCWarning(lcVfsXAttr) << u"Failed to get inode for" << targetPath; + qCWarning(lcVfsXAttr) << u"Failed to get inode for" << targetPath.native(); } // Update the client sync journal database if the file modifications have been successful const auto result = this->params().journal->setFileRecord(SyncJournalFileRecord::fromSyncFileItem(*item)); if (!result) { qCWarning(lcVfsXAttr) << u"Error when setting the file record to the database" << result.error(); } else { - qCInfo(lcVfsXAttr) << u"Hydration succeeded" << targetPath; + qCInfo(lcVfsXAttr) << u"Hydration succeeded" << targetPath.native(); } } else { - qCWarning(lcVfsXAttr) << u"Hydration succeeded but the file appears to be moved" << targetPath; + qCWarning(lcVfsXAttr) << u"Hydration succeeded but the file appears to be moved" << targetPath.native(); } hydration->deleteLater();