Split the CfAPI lower level code in a wrapper
authorKevin Ottens <kevin.ottens@nextcloud.com>
Wed, 23 Dec 2020 16:38:05 +0000 (17:38 +0100)
committerKevin Ottens <kevin.ottens@nextcloud.com>
Wed, 30 Dec 2020 08:44:58 +0000 (09:44 +0100)
Signed-off-by: Kevin Ottens <kevin.ottens@nextcloud.com>
src/libsync/CMakeLists.txt
src/libsync/vfs/cfapi/cfapiwrapper.cpp [new file with mode: 0644]
src/libsync/vfs/cfapi/cfapiwrapper.h [new file with mode: 0644]
src/libsync/vfs/cfapi/vfs_cfapi.cpp

index 1f1dda281a5a483a208156fcad97d3df066d92a6..aa482accccf48582231123668110f4b9f10fb486 100644 (file)
@@ -62,7 +62,10 @@ set(libsync_SRCS
 )
 
 if (WIN32)
-    set (libsync_SRCS ${libsync_SRCS} vfs/cfapi/vfs_cfapi.cpp)
+    set(libsync_SRCS ${libsync_SRCS}
+        vfs/cfapi/cfapiwrapper.cpp
+        vfs/cfapi/vfs_cfapi.cpp
+    )
     add_definitions(-D_WIN32_WINNT=_WIN32_WINNT_WIN10)
     list(APPEND OS_SPECIFIC_LINK_LIBRARIES cldapi)
 endif()
diff --git a/src/libsync/vfs/cfapi/cfapiwrapper.cpp b/src/libsync/vfs/cfapi/cfapiwrapper.cpp
new file mode 100644 (file)
index 0000000..c2c0ecc
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) by Kevin Ottens <kevin.ottens@nextcloud.com>
+ *
+ * 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 "cfapiwrapper.h"
+
+#include "common/utility.h"
+
+#include <QDir>
+#include <QFileInfo>
+#include <QLoggingCategory>
+
+#include <cfapi.h>
+#include <comdef.h>
+
+Q_LOGGING_CATEGORY(lcCfApiWrapper, "nextcloud.sync.vfs.cfapi.wrapper", QtInfoMsg)
+
+namespace {
+void CALLBACK cfApiFetchDataCallback(_In_ CONST CF_CALLBACK_INFO* callbackInfo, _In_ CONST CF_CALLBACK_PARAMETERS* callbackParameters)
+{
+    qCCritical(lcCfApiWrapper()) << "Got in!";
+    Q_ASSERT(false);
+}
+
+CF_CALLBACK_REGISTRATION cfApiCallbacks[] = {
+    { CF_CALLBACK_TYPE_FETCH_DATA, cfApiFetchDataCallback },
+    CF_CALLBACK_REGISTRATION_END
+};
+
+DWORD sizeToDWORD(size_t size)
+{
+    return OCC::Utility::convertSizeToDWORD(size);
+}
+
+void deletePlaceholderInfo(CF_PLACEHOLDER_BASIC_INFO *info)
+{
+    auto byte = reinterpret_cast<char *>(info);
+    delete[] byte;
+}
+
+std::wstring pathForHandle(const OCC::CfApiWrapper::FileHandle &handle)
+{
+    wchar_t buffer[MAX_PATH];
+    const qint64 result = GetFinalPathNameByHandle(handle.get(), buffer, MAX_PATH, VOLUME_NAME_DOS);
+    Q_ASSERT(result < MAX_PATH);
+    return std::wstring(buffer);
+}
+
+OCC::PinState cfPinStateToPinState(CF_PIN_STATE state)
+{
+    switch (state) {
+    case CF_PIN_STATE_UNSPECIFIED:
+        return OCC::PinState::Unspecified;
+    case CF_PIN_STATE_PINNED:
+        return OCC::PinState::AlwaysLocal;
+    case CF_PIN_STATE_UNPINNED:
+        return OCC::PinState::OnlineOnly;
+    case CF_PIN_STATE_INHERIT:
+        return OCC::PinState::Inherited;
+    default:
+        Q_UNREACHABLE();
+        return OCC::PinState::Inherited;
+    }
+}
+
+CF_PIN_STATE pinStateToCfPinState(OCC::PinState state)
+{
+    switch (state) {
+    case OCC::PinState::Inherited:
+        return CF_PIN_STATE_INHERIT;
+    case OCC::PinState::AlwaysLocal:
+        return CF_PIN_STATE_PINNED;
+    case OCC::PinState::OnlineOnly:
+        return CF_PIN_STATE_UNPINNED;
+    case OCC::PinState::Unspecified:
+        return CF_PIN_STATE_UNSPECIFIED;
+    default:
+        Q_UNREACHABLE();
+        return CF_PIN_STATE_UNSPECIFIED;
+    }
+}
+
+CF_SET_PIN_FLAGS pinRecurseModeToCfSetPinFlags(OCC::CfApiWrapper::SetPinRecurseMode mode)
+{
+    switch (mode) {
+    case OCC::CfApiWrapper::NoRecurse:
+        return CF_SET_PIN_FLAG_NONE;
+    case OCC::CfApiWrapper::Recurse:
+        return CF_SET_PIN_FLAG_RECURSE;
+    case OCC::CfApiWrapper::ChildrenOnly:
+        return CF_SET_PIN_FLAG_RECURSE_ONLY;
+    default:
+        Q_UNREACHABLE();
+        return CF_SET_PIN_FLAG_NONE;
+    }
+}
+
+}
+
+OCC::CfApiWrapper::ConnectionKey::ConnectionKey()
+    : _data(new CF_CONNECTION_KEY, [](void *p) { delete reinterpret_cast<CF_CONNECTION_KEY *>(p); })
+{
+}
+
+OCC::CfApiWrapper::FileHandle::FileHandle()
+    : _data(nullptr, [](void *) {})
+{
+}
+
+OCC::CfApiWrapper::FileHandle::FileHandle(void *data, Deleter deleter)
+    : _data(data, deleter)
+{
+}
+
+OCC::CfApiWrapper::PlaceHolderInfo::PlaceHolderInfo()
+    : _data(nullptr, [](CF_PLACEHOLDER_BASIC_INFO *) {})
+{
+}
+
+OCC::CfApiWrapper::PlaceHolderInfo::PlaceHolderInfo(CF_PLACEHOLDER_BASIC_INFO *data, Deleter deleter)
+    : _data(data, deleter)
+{
+}
+
+OCC::Optional<OCC::PinStateEnums::PinState> OCC::CfApiWrapper::PlaceHolderInfo::pinState() const
+{
+    Q_ASSERT(_data);
+    if (!_data) {
+        return {};
+    }
+
+    return cfPinStateToPinState(_data->PinState);
+}
+
+OCC::Result<void, QString> OCC::CfApiWrapper::registerSyncRoot(const QString &path, const QString &providerName, const QString &providerVersion)
+{
+    const auto p = path.toStdWString();
+    const auto name = providerName.toStdWString();
+    const auto version = providerVersion.toStdWString();
+
+    CF_SYNC_REGISTRATION info;
+    info.ProviderName = name.data();
+    info.ProviderVersion = version.data();
+    info.SyncRootIdentity = nullptr;
+    info.SyncRootIdentityLength = 0;
+    info.FileIdentity = nullptr;
+    info.FileIdentityLength = 0;
+
+    CF_SYNC_POLICIES policies;
+    policies.Hydration.Primary = CF_HYDRATION_POLICY_FULL;
+    policies.Hydration.Modifier = CF_HYDRATION_POLICY_MODIFIER_NONE;
+    policies.Population.Primary = CF_POPULATION_POLICY_ALWAYS_FULL;
+    policies.Population.Modifier = CF_POPULATION_POLICY_MODIFIER_NONE;
+    policies.InSync = CF_INSYNC_POLICY_PRESERVE_INSYNC_FOR_SYNC_ENGINE;
+    policies.HardLink = CF_HARDLINK_POLICY_NONE;
+
+    const qint64 result = CfRegisterSyncRoot(p.data(), &info, &policies, CF_REGISTER_FLAG_UPDATE);
+    Q_ASSERT(result == S_OK);
+    if (result != S_OK) {
+        return QString::fromWCharArray(_com_error(result).ErrorMessage());
+    } else {
+        return {};
+    }
+}
+
+OCC::Result<void, QString> OCC::CfApiWrapper::unegisterSyncRoot(const QString &path)
+{
+    const auto p = path.toStdWString();
+    const qint64 result = CfUnregisterSyncRoot(p.data());
+    Q_ASSERT(result == S_OK);
+    if (result != S_OK) {
+        return QString::fromWCharArray(_com_error(result).ErrorMessage());
+    } else {
+        return {};
+    }
+}
+
+OCC::Result<OCC::CfApiWrapper::ConnectionKey, QString> OCC::CfApiWrapper::connectSyncRoot(const QString &path, OCC::VfsCfApi *context)
+{
+    auto key = ConnectionKey();
+    const auto p = path.toStdWString();
+    const qint64 result = CfConnectSyncRoot(p.data(),
+                                            cfApiCallbacks,
+                                            context,
+                                            CF_CONNECT_FLAG_REQUIRE_PROCESS_INFO | CF_CONNECT_FLAG_REQUIRE_FULL_FILE_PATH,
+                                            static_cast<CF_CONNECTION_KEY *>(key.get()));
+    Q_ASSERT(result == S_OK);
+    if (result != S_OK) {
+        return QString::fromWCharArray(_com_error(result).ErrorMessage());
+    } else {
+        return key;
+    }
+}
+
+OCC::Result<void, QString> OCC::CfApiWrapper::disconnectSyncRoot(ConnectionKey &&key)
+{
+    const qint64 result = CfDisconnectSyncRoot(*static_cast<CF_CONNECTION_KEY *>(key.get()));
+    Q_ASSERT(result == S_OK);
+    if (result != S_OK) {
+        return QString::fromWCharArray(_com_error(result).ErrorMessage());
+    } else {
+        return {};
+    }
+}
+
+bool OCC::CfApiWrapper::isSparseFile(const QString &path)
+{
+    const auto p = path.toStdWString();
+    const auto attributes = GetFileAttributes(p.data());
+    return (attributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0;
+}
+
+OCC::CfApiWrapper::FileHandle OCC::CfApiWrapper::handleForPath(const QString &path)
+{
+    if (QFileInfo(path).isDir()) {
+        HANDLE handle = nullptr;
+        const qint64 openResult = CfOpenFileWithOplock(path.toStdWString().data(), CF_OPEN_FILE_FLAG_NONE, &handle);
+        if (openResult == S_OK) {
+            return {handle, CfCloseHandle};
+        }
+    } else {
+        const auto handle = CreateFile(path.toStdWString().data(), 0, 0, nullptr,
+                                       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+        if (handle != INVALID_HANDLE_VALUE) {
+            return {handle, [](HANDLE h) { CloseHandle(h); }};
+        }
+    }
+
+    return {};
+}
+
+OCC::CfApiWrapper::PlaceHolderInfo OCC::CfApiWrapper::findPlaceholderInfo(const FileHandle &handle)
+{
+    Q_ASSERT(handle);
+
+    constexpr auto fileIdMaxLength = 128;
+    const auto infoSize = sizeof(CF_PLACEHOLDER_BASIC_INFO) + fileIdMaxLength;
+    auto info = PlaceHolderInfo(reinterpret_cast<CF_PLACEHOLDER_BASIC_INFO *>(new char[infoSize]), deletePlaceholderInfo);
+    const qint64 result = CfGetPlaceholderInfo(handle.get(), CF_PLACEHOLDER_INFO_BASIC, info.get(), sizeToDWORD(infoSize), nullptr);
+
+    if (result == S_OK) {
+        return info;
+    } else {
+        return {};
+    }
+}
+
+OCC::Result<void, QString> OCC::CfApiWrapper::setPinState(const FileHandle &handle, PinState state, SetPinRecurseMode mode)
+{
+    const auto cfState = pinStateToCfPinState(state);
+    const auto flags = pinRecurseModeToCfSetPinFlags(mode);
+
+    const qint64 result = CfSetPinState(handle.get(), cfState, flags, nullptr);
+    if (result == S_OK) {
+        return {};
+    } else {
+        qCWarning(lcCfApiWrapper) << "Couldn't set pin state" << state << "for" << pathForHandle(handle) << "with recurse mode" << mode << ":" << _com_error(result).ErrorMessage();
+        return "Couldn't set pin state";
+    }
+}
+
+OCC::Result<void, QString> OCC::CfApiWrapper::createPlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId)
+{
+    const auto fileInfo = QFileInfo(path);
+    const auto localBasePath = QDir::toNativeSeparators(fileInfo.path()).toStdWString();
+    const auto relativePath = fileInfo.fileName().toStdWString();
+
+    const auto fileIdentity = QString::fromUtf8(fileId).toStdWString();
+
+    CF_PLACEHOLDER_CREATE_INFO cloudEntry;
+    cloudEntry.FileIdentity = fileIdentity.data();
+    const auto fileIdentitySize = (fileIdentity.length() + 1) * sizeof(wchar_t);
+    cloudEntry.FileIdentityLength = sizeToDWORD(fileIdentitySize);
+
+    cloudEntry.RelativeFileName = relativePath.data();
+    cloudEntry.Flags = CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC;
+    cloudEntry.FsMetadata.FileSize.QuadPart = size;
+    cloudEntry.FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
+    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &cloudEntry.FsMetadata.BasicInfo.CreationTime);
+    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &cloudEntry.FsMetadata.BasicInfo.LastWriteTime);
+    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &cloudEntry.FsMetadata.BasicInfo.LastAccessTime);
+    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &cloudEntry.FsMetadata.BasicInfo.ChangeTime);
+
+    if (fileInfo.isDir()) {
+        cloudEntry.Flags |= CF_PLACEHOLDER_CREATE_FLAG_DISABLE_ON_DEMAND_POPULATION;
+        cloudEntry.FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
+        cloudEntry.FsMetadata.FileSize.QuadPart = 0;
+    }
+
+    const qint64 result = CfCreatePlaceholders(localBasePath.data(), &cloudEntry, 1, CF_CREATE_FLAG_NONE, nullptr);
+    if (result != S_OK) {
+        qCWarning(lcCfApiWrapper) << "Couldn't create placeholder info for" << path << ":" << _com_error(result).ErrorMessage();
+        return "Couldn't create placeholder info";
+    }
+
+    const auto parentHandle = handleForPath(QDir::toNativeSeparators(QFileInfo(path).absolutePath()));
+    const auto parentInfo = findPlaceholderInfo(parentHandle);
+    const auto state = parentInfo && parentInfo->PinState == CF_PIN_STATE_UNPINNED ? CF_PIN_STATE_UNPINNED : CF_PIN_STATE_INHERIT;
+
+    const auto handle = handleForPath(path);
+    if (!setPinState(handle, cfPinStateToPinState(state), NoRecurse)) {
+        return "Couldn't set the default inherit pin state";
+    }
+
+    return {};
+}
+
+OCC::Result<void, QString> OCC::CfApiWrapper::updatePlaceholderInfo(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath)
+{
+    Q_ASSERT(handle);
+
+    const auto info = replacesPath.isEmpty() ? findPlaceholderInfo(handle)
+                                             : findPlaceholderInfo(handleForPath(replacesPath));
+    if (!info) {
+        return "Can't update non existing placeholder info";
+    }
+
+    const auto previousPinState = cfPinStateToPinState(info->PinState);
+    const auto fileIdentity = QString::fromUtf8(fileId).toStdWString();
+    const auto fileIdentitySize = (fileIdentity.length() + 1) * sizeof(wchar_t);
+
+    CF_FS_METADATA metadata;
+    metadata.FileSize.QuadPart = size;
+    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &metadata.BasicInfo.CreationTime);
+    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &metadata.BasicInfo.LastWriteTime);
+    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &metadata.BasicInfo.LastAccessTime);
+    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &metadata.BasicInfo.ChangeTime);
+
+    const qint64 result = CfUpdatePlaceholder(handle.get(), &metadata,
+                                              fileIdentity.data(), sizeToDWORD(fileIdentitySize),
+                                              nullptr, 0, CF_UPDATE_FLAG_NONE, nullptr, nullptr);
+
+    if (result != S_OK) {
+        qCWarning(lcCfApiWrapper) << "Couldn't update placeholder info for" << pathForHandle(handle) << ":" << _com_error(result).ErrorMessage();
+        return "Couldn't update placeholder info";
+    }
+
+    // Pin state tends to be lost on updates, so restore it every time
+    if (!setPinState(handle, previousPinState, NoRecurse)) {
+        return "Couldn't restore pin state";
+    }
+
+    return {};
+}
+
+OCC::Result<void, QString> OCC::CfApiWrapper::convertToPlaceholder(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath)
+{
+    Q_ASSERT(handle);
+
+    const auto fileIdentity = QString::fromUtf8(fileId).toStdWString();
+    const auto fileIdentitySize = (fileIdentity.length() + 1) * sizeof(wchar_t);
+    const qint64 result = CfConvertToPlaceholder(handle.get(), fileIdentity.data(), sizeToDWORD(fileIdentitySize), CF_CONVERT_FLAG_NONE, nullptr, nullptr);
+    Q_ASSERT(result == S_OK);
+    if (result != S_OK) {
+        qCCritical(lcCfApiWrapper) << "Couldn't convert to placeholder" << pathForHandle(handle) << ":" << _com_error(result).ErrorMessage();
+        return "Couldn't convert to placeholder";
+    }
+
+    const auto originalHandle = handleForPath(replacesPath);
+    const auto originalInfo = originalHandle ? findPlaceholderInfo(originalHandle) : PlaceHolderInfo(nullptr, deletePlaceholderInfo);
+    if (!originalInfo) {
+        const auto stateResult = setPinState(handle, PinState::Inherited, NoRecurse);
+        Q_ASSERT(stateResult);
+        return stateResult;
+    } else {
+        const auto state = cfPinStateToPinState(originalInfo->PinState);
+        const auto stateResult = setPinState(handle, state, NoRecurse);
+        Q_ASSERT(stateResult);
+        return stateResult;
+    }
+}
diff --git a/src/libsync/vfs/cfapi/cfapiwrapper.h b/src/libsync/vfs/cfapi/cfapiwrapper.h
new file mode 100644 (file)
index 0000000..888242b
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) by Kevin Ottens <kevin.ottens@nextcloud.com>
+ *
+ * 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 <memory>
+
+#include "owncloudlib.h"
+#include "common/pinstate.h"
+#include "common/result.h"
+
+struct CF_PLACEHOLDER_BASIC_INFO;
+
+namespace OCC {
+
+class VfsCfApi;
+
+namespace CfApiWrapper
+{
+
+class OWNCLOUDSYNC_EXPORT ConnectionKey
+{
+public:
+    ConnectionKey();
+    inline void *get() const { return _data.get(); }
+
+private:
+    std::unique_ptr<void, void(*)(void *)> _data;
+};
+
+class OWNCLOUDSYNC_EXPORT FileHandle
+{
+public:
+    using Deleter = void (*)(void *);
+
+    FileHandle();
+    FileHandle(void *data, Deleter deleter);
+
+    inline void *get() const { return _data.get(); }
+    inline explicit operator bool() const noexcept { return static_cast<bool>(_data); }
+
+private:
+    std::unique_ptr<void, void(*)(void *)> _data;
+};
+
+class OWNCLOUDSYNC_EXPORT PlaceHolderInfo
+{
+public:
+    using Deleter = void (*)(CF_PLACEHOLDER_BASIC_INFO *);
+
+    PlaceHolderInfo();
+    PlaceHolderInfo(CF_PLACEHOLDER_BASIC_INFO *data, Deleter deleter);
+
+    inline CF_PLACEHOLDER_BASIC_INFO *get() const noexcept { return _data.get(); }
+    inline CF_PLACEHOLDER_BASIC_INFO *operator->() const noexcept { return _data.get(); }
+    inline explicit operator bool() const noexcept { return static_cast<bool>(_data); }
+
+    Optional<PinState> pinState() const;
+
+private:
+    std::unique_ptr<CF_PLACEHOLDER_BASIC_INFO, Deleter> _data;
+};
+
+OWNCLOUDSYNC_EXPORT Result<void, QString> registerSyncRoot(const QString &path, const QString &providerName, const QString &providerVersion);
+OWNCLOUDSYNC_EXPORT Result<void, QString> unegisterSyncRoot(const QString &path);
+
+OWNCLOUDSYNC_EXPORT Result<ConnectionKey, QString> connectSyncRoot(const QString &path, VfsCfApi *context);
+OWNCLOUDSYNC_EXPORT Result<void, QString> disconnectSyncRoot(ConnectionKey &&key);
+
+OWNCLOUDSYNC_EXPORT bool isSparseFile(const QString &path);
+
+OWNCLOUDSYNC_EXPORT FileHandle handleForPath(const QString &path);
+
+OWNCLOUDSYNC_EXPORT PlaceHolderInfo findPlaceholderInfo(const FileHandle &handle);
+
+enum SetPinRecurseMode {
+    NoRecurse = 0,
+    Recurse,
+    ChildrenOnly
+};
+
+OWNCLOUDSYNC_EXPORT Result<void, QString> setPinState(const FileHandle &handle, PinState state, SetPinRecurseMode mode);
+OWNCLOUDSYNC_EXPORT Result<void, QString> createPlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId);
+OWNCLOUDSYNC_EXPORT Result<void, QString> updatePlaceholderInfo(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath = QString());
+OWNCLOUDSYNC_EXPORT Result<void, QString> convertToPlaceholder(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath);
+
+}
+
+} // namespace OCC
index c43efcbf47f0f70250b3d568d4f502ea59ad5776..4268f51b6d3434dc32c934f370f91b55a7ec47b8 100644 (file)
@@ -17,6 +17,7 @@
 #include <QDir>
 #include <QFile>
 
+#include "cfapiwrapper.h"
 #include "syncfileitem.h"
 #include "filesystem.h"
 #include "common/syncjournaldb.h"
 
 Q_LOGGING_CATEGORY(lcCfApi, "nextcloud.sync.vfs.cfapi", QtInfoMsg)
 
-namespace {
-void CALLBACK cfApiFetchDataCallback(_In_ CONST CF_CALLBACK_INFO* callbackInfo, _In_ CONST CF_CALLBACK_PARAMETERS* callbackParameters)
-{
-    qCCritical(lcCfApi) << "Got in!";
-    Q_ASSERT(false);
-}
-
-CF_CALLBACK_REGISTRATION cfApiCallbacks[] = {
-    { CF_CALLBACK_TYPE_FETCH_DATA, cfApiFetchDataCallback },
-    CF_CALLBACK_REGISTRATION_END
-};
-
-std::unique_ptr<void, void(*)(HANDLE)> handleForPath(const QString &path)
-{
-    if (QFileInfo(path).isDir()) {
-        HANDLE handle = nullptr;
-        const qint64 openResult = CfOpenFileWithOplock(path.toStdWString().data(), CF_OPEN_FILE_FLAG_NONE, &handle);
-        if (openResult == S_OK) {
-            return {handle, CfCloseHandle};
-        }
-    } else {
-        const auto handle = CreateFile(path.toStdWString().data(), 0, 0, nullptr,
-                                       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
-        if (handle != INVALID_HANDLE_VALUE) {
-            return {handle, [](HANDLE h) { CloseHandle(h); }};
-        }
-    }
-
-    return {nullptr, [](HANDLE){}};
-}
-
-DWORD sizeToDWORD(size_t size)
-{
-    return OCC::Utility::convertSizeToDWORD(size);
-}
-
-void deletePlaceholderInfo(CF_PLACEHOLDER_BASIC_INFO *info)
-{
-    auto byte = reinterpret_cast<char *>(info);
-    delete[] byte;
-}
-
-std::unique_ptr<CF_PLACEHOLDER_BASIC_INFO, decltype(&deletePlaceholderInfo)> findPlaceholderInfo(const QString &path)
-{
-    auto handle = handleForPath(path);
-    if (!handle) {
-        return {nullptr, deletePlaceholderInfo};
-    }
-
-    constexpr auto fileIdMaxLength = 128;
-    const auto infoSize = sizeof(CF_PLACEHOLDER_BASIC_INFO) + fileIdMaxLength;
-    std::unique_ptr<CF_PLACEHOLDER_BASIC_INFO, decltype(&deletePlaceholderInfo)> info(reinterpret_cast<CF_PLACEHOLDER_BASIC_INFO *>(new char[infoSize]), deletePlaceholderInfo);
-    const qint64 result = CfGetPlaceholderInfo(handle.get(), CF_PLACEHOLDER_INFO_BASIC, info.get(), sizeToDWORD(infoSize), nullptr);
-
-    if (result == S_OK) {
-        return info;
-    } else {
-        return {nullptr, deletePlaceholderInfo};
-    }
-}
-
-bool setPinState(const QString &path, CF_PIN_STATE state, CF_SET_PIN_FLAGS flags)
-{
-    if (!findPlaceholderInfo(path)) {
-        return false;
-    }
-
-    const auto handle = handleForPath(path);
-    if (!handle) {
-        return false;
-    }
-
-    const qint64 result = CfSetPinState(handle.get(), state, flags, nullptr);
-    if (result != S_OK) {
-        qCWarning(lcCfApi) << "Couldn't set pin state for" << path << ":" << _com_error(result).ErrorMessage();
-    }
-    return result == S_OK;
-}
-
-OCC::Result<void, QString> createPlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId)
-{
-    const auto fileInfo = QFileInfo(path);
-    const auto localBasePath = QDir::toNativeSeparators(fileInfo.path()).toStdWString();
-    const auto relativePath = fileInfo.fileName().toStdWString();
-
-    const auto fileIdentity = QString::fromUtf8(fileId).toStdWString();
-
-    CF_PLACEHOLDER_CREATE_INFO cloudEntry;
-    cloudEntry.FileIdentity = fileIdentity.data();
-    const auto fileIdentitySize = (fileIdentity.length() + 1) * sizeof(wchar_t);
-    cloudEntry.FileIdentityLength = sizeToDWORD(fileIdentitySize);
-
-    cloudEntry.RelativeFileName = relativePath.data();
-    cloudEntry.Flags = CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC;
-    cloudEntry.FsMetadata.FileSize.QuadPart = size;
-    cloudEntry.FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
-    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &cloudEntry.FsMetadata.BasicInfo.CreationTime);
-    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &cloudEntry.FsMetadata.BasicInfo.LastWriteTime);
-    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &cloudEntry.FsMetadata.BasicInfo.LastAccessTime);
-    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &cloudEntry.FsMetadata.BasicInfo.ChangeTime);
-
-    if (fileInfo.isDir()) {
-        cloudEntry.Flags |= CF_PLACEHOLDER_CREATE_FLAG_DISABLE_ON_DEMAND_POPULATION;
-        cloudEntry.FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
-        cloudEntry.FsMetadata.FileSize.QuadPart = 0;
-    }
-
-    const qint64 result = CfCreatePlaceholders(localBasePath.data(), &cloudEntry, 1, CF_CREATE_FLAG_NONE, nullptr);
-    if (result != S_OK) {
-        qCWarning(lcCfApi) << "Couldn't create placeholder info for" << path << ":" << _com_error(result).ErrorMessage();
-        return "Couldn't create placeholder info";
-    }
-
-    const auto parentInfo = findPlaceholderInfo(QDir::toNativeSeparators(QFileInfo(path).absolutePath()));
-    const auto state = parentInfo && parentInfo->PinState == CF_PIN_STATE_UNPINNED ? CF_PIN_STATE_UNPINNED : CF_PIN_STATE_INHERIT;
-
-    if (!setPinState(path, state, CF_SET_PIN_FLAG_NONE)) {
-        return "Couldn't set the default inherit pin state";
-    }
-
-    return {};
-}
-
-OCC::Result<void, QString> updatePlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath = QString())
-{
-    auto info = findPlaceholderInfo(replacesPath.isEmpty() ? path : replacesPath);
-    if (!info) {
-        return "Can't update non existing placeholder info";
-    }
-
-    const auto previousPinState = info->PinState;
-
-    auto handle = handleForPath(path);
-    if (!handle) {
-        return "Can't update placeholder info for non existing file";
-    }
-
-    const auto fileIdentity = QString::fromUtf8(fileId).toStdWString();
-    const auto fileIdentitySize = (fileIdentity.length() + 1) * sizeof(wchar_t);
-
-    CF_FS_METADATA metadata;
-    metadata.FileSize.QuadPart = size;
-    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &metadata.BasicInfo.CreationTime);
-    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &metadata.BasicInfo.LastWriteTime);
-    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &metadata.BasicInfo.LastAccessTime);
-    OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &metadata.BasicInfo.ChangeTime);
-
-    const qint64 result = CfUpdatePlaceholder(handle.get(), &metadata,
-                                              fileIdentity.data(), sizeToDWORD(fileIdentitySize),
-                                              nullptr, 0, CF_UPDATE_FLAG_NONE, nullptr, nullptr);
-
-    if (result != S_OK) {
-        qCWarning(lcCfApi) << "Couldn't update placeholder info for" << path << ":" << _com_error(result).ErrorMessage();
-        return "Couldn't update placeholder info";
-    }
-
-    // Pin state tends to be lost on updates, so restore it every time
-    if (!setPinState(path, previousPinState, CF_SET_PIN_FLAG_NONE)) {
-        return "Couldn't restore pin state";
-    }
-
-    return {};
-}
-
-void convertToPlaceholder(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath)
-{
-    auto handle = handleForPath(path);
-    Q_ASSERT(handle);
-
-    const auto fileIdentity = QString::fromUtf8(fileId).toStdWString();
-    const auto fileIdentitySize = (fileIdentity.length() + 1) * sizeof(wchar_t);
-    const qint64 result = CfConvertToPlaceholder(handle.get(), fileIdentity.data(), sizeToDWORD(fileIdentitySize), CF_CONVERT_FLAG_NONE, nullptr, nullptr);
-    Q_ASSERT(result == S_OK);
-    if (result != S_OK) {
-        qCCritical(lcCfApi) << "Couldn't convert to placeholder" << path << ":" << _com_error(result).ErrorMessage();
-        return;
-    }
-
-    const auto originalInfo = findPlaceholderInfo(replacesPath);
-    if (!originalInfo) {
-        const auto stateResult = setPinState(path, CF_PIN_STATE_INHERIT, CF_SET_PIN_FLAG_NONE);
-        Q_ASSERT(stateResult);
-    } else {
-        const auto stateResult = setPinState(path, originalInfo->PinState, CF_SET_PIN_FLAG_NONE);
-        Q_ASSERT(stateResult);
-    }
+namespace cfapi {
+using namespace OCC::CfApiWrapper;
 }
 
 namespace OCC {
@@ -219,7 +36,7 @@ namespace OCC {
 class VfsCfApiPrivate
 {
 public:
-    CF_CONNECTION_KEY callbackConnectionKey = {};
+    cfapi::ConnectionKey connectionKey;
 };
 
 VfsCfApi::VfsCfApi(QObject *parent)
@@ -244,53 +61,36 @@ void VfsCfApi::startImpl(const VfsSetupParams &params)
 {
     const auto localPath = QDir::toNativeSeparators(params.filesystemPath);
 
-    const auto providerName = params.providerName.toStdWString();
-    const auto providerVersion = params.providerVersion.toStdWString();
-
-    CF_SYNC_REGISTRATION info;
-    info.ProviderName = providerName.data();
-    info.ProviderVersion = providerVersion.data();
-    info.SyncRootIdentity = nullptr;
-    info.SyncRootIdentityLength = 0;
-    info.FileIdentity = nullptr;
-    info.FileIdentityLength = 0;
-
-    CF_SYNC_POLICIES policies;
-    policies.Hydration.Primary = CF_HYDRATION_POLICY_FULL;
-    policies.Hydration.Modifier = CF_HYDRATION_POLICY_MODIFIER_NONE;
-    policies.Population.Primary = CF_POPULATION_POLICY_ALWAYS_FULL;
-    policies.Population.Modifier = CF_POPULATION_POLICY_MODIFIER_NONE;
-    policies.InSync = CF_INSYNC_POLICY_PRESERVE_INSYNC_FOR_SYNC_ENGINE;
-    policies.HardLink = CF_HARDLINK_POLICY_NONE;
-
-    const qint64 registerResult = CfRegisterSyncRoot(localPath.toStdWString().data(), &info, &policies, CF_REGISTER_FLAG_UPDATE);
-    Q_ASSERT(registerResult == S_OK);
-    if (registerResult != S_OK) {
-        qCCritical(lcCfApi) << "Initialization failed, couldn't register sync root:" << _com_error(registerResult).ErrorMessage();
+    const auto registerResult = cfapi::registerSyncRoot(localPath, params.providerName, params.providerVersion);
+    if (!registerResult) {
+        qCCritical(lcCfApi) << "Initialization failed, couldn't register sync root:" << registerResult.error();
         return;
     }
 
-
-    const qint64 connectResult = CfConnectSyncRoot(localPath.toStdWString().data(),
-                                                   cfApiCallbacks,
-                                                   nullptr,
-                                                   CF_CONNECT_FLAG_REQUIRE_PROCESS_INFO | CF_CONNECT_FLAG_REQUIRE_FULL_FILE_PATH,
-                                                   &d->callbackConnectionKey);
-    Q_ASSERT(connectResult == S_OK);
-    if (connectResult != S_OK) {
-        qCCritical(lcCfApi) << "Initialization failed, couldn't connect sync root:" << _com_error(connectResult).ErrorMessage();
+    auto connectResult = cfapi::connectSyncRoot(localPath, this);
+    if (!connectResult) {
+        qCCritical(lcCfApi) << "Initialization failed, couldn't connect sync root:" << connectResult.error();
+        return;
     }
+
+    d->connectionKey = *std::move(connectResult);
 }
 
 void VfsCfApi::stop()
 {
-    CfDisconnectSyncRoot(d->callbackConnectionKey);
+    const auto result = cfapi::disconnectSyncRoot(std::move(d->connectionKey));
+    if (!result) {
+        qCCritical(lcCfApi) << "Disconnect failed for" << QDir::toNativeSeparators(params().filesystemPath) << ":" << result.error();
+    }
 }
 
 void VfsCfApi::unregisterFolder()
 {
-    const auto localPath = QDir::toNativeSeparators(params().filesystemPath).toStdWString();
-    CfUnregisterSyncRoot(localPath.data());
+    const auto localPath = QDir::toNativeSeparators(params().filesystemPath);
+    const auto result = cfapi::unegisterSyncRoot(localPath);
+    if (!result) {
+        qCCritical(lcCfApi) << "Unregistration failed for" << localPath << ":" << result.error();
+    }
 }
 
 bool VfsCfApi::socketApiPinStateActionsShown() const
@@ -306,14 +106,20 @@ bool VfsCfApi::isHydrating() const
 Result<void, QString> VfsCfApi::updateMetadata(const QString &filePath, time_t modtime, qint64 size, const QByteArray &fileId)
 {
     const auto localPath = QDir::toNativeSeparators(filePath);
-    return updatePlaceholderInfo(localPath, modtime, size, fileId);
+    const auto handle = cfapi::handleForPath(localPath);
+    if (handle) {
+        return cfapi::updatePlaceholderInfo(handle, modtime, size, fileId);
+    } else {
+        qCWarning(lcCfApi) << "Couldn't update metadata for non existing file" << localPath;
+        return "Couldn't update metadata";
+    }
 }
 
 Result<void, QString> VfsCfApi::createPlaceholder(const SyncFileItem &item)
 {
     Q_ASSERT(params().filesystemPath.endsWith('/'));
     const auto localPath = QDir::toNativeSeparators(params().filesystemPath + item._file);
-    const auto result = createPlaceholderInfo(localPath, item._modtime, item._size, item._fileId);
+    const auto result = cfapi::createPlaceholderInfo(localPath, item._modtime, item._size, item._fileId);
     return result;
 }
 
@@ -345,10 +151,12 @@ void VfsCfApi::convertToPlaceholder(const QString &filename, const SyncFileItem
 {
     const auto localPath = QDir::toNativeSeparators(filename);
     const auto replacesPath = QDir::toNativeSeparators(replacesFile);
-    if (findPlaceholderInfo(localPath)) {
-        updatePlaceholderInfo(localPath, item._modtime, item._size, item._fileId, replacesPath);
+
+    const auto handle = cfapi::handleForPath(localPath);
+    if (cfapi::findPlaceholderInfo(handle)) {
+        cfapi::updatePlaceholderInfo(handle, item._modtime, item._size, item._fileId, replacesPath);
     } else {
-        ::convertToPlaceholder(localPath, item._modtime, item._size, item._fileId, replacesPath);
+        cfapi::convertToPlaceholder(handle, item._modtime, item._size, item._fileId, replacesPath);
     }
 }
 
@@ -359,9 +167,8 @@ bool VfsCfApi::needsMetadataUpdate(const SyncFileItem &item)
 
 bool VfsCfApi::isDehydratedPlaceholder(const QString &filePath)
 {
-    const auto path = QDir::toNativeSeparators(filePath).toStdWString();
-    const auto attributes = GetFileAttributes(path.data());
-    return (attributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0;
+    const auto path = QDir::toNativeSeparators(filePath);
+    return cfapi::isSparseFile(path);
 }
 
 bool VfsCfApi::statTypeVirtualFile(csync_file_stat_t *stat, void *statData)
@@ -398,50 +205,35 @@ bool VfsCfApi::statTypeVirtualFile(csync_file_stat_t *stat, void *statData)
 bool VfsCfApi::setPinState(const QString &folderPath, PinState state)
 {
     const auto localPath = QDir::toNativeSeparators(params().filesystemPath + folderPath);
-
-    auto cfState = CF_PIN_STATE_UNSPECIFIED;
-    switch (state) {
-    case PinState::AlwaysLocal:
-        cfState = CF_PIN_STATE_PINNED;
-        break;
-    case PinState::Inherited:
-        cfState = CF_PIN_STATE_INHERIT;
-        break;
-    case PinState::OnlineOnly:
-        cfState = CF_PIN_STATE_UNPINNED;
-        break;
-    case PinState::Unspecified:
-        cfState = CF_PIN_STATE_UNSPECIFIED;
-        break;
-    default:
-        Q_UNREACHABLE();
+    const auto handle = cfapi::handleForPath(localPath);
+    if (handle) {
+        if (cfapi::setPinState(handle, state, cfapi::Recurse)) {
+            return true;
+        } else {
+            return false;
+        }
+    } else {
+        qCWarning(lcCfApi) << "Couldn't update pin state for non existing file" << localPath;
         return false;
     }
-
-    return ::setPinState(localPath, cfState, CF_SET_PIN_FLAG_RECURSE);
 }
 
 Optional<PinState> VfsCfApi::pinState(const QString &folderPath)
 {
     const auto localPath = QDir::toNativeSeparators(params().filesystemPath + folderPath);
-    const auto info = findPlaceholderInfo(localPath);
-    if (!info) {
+    const auto handle = cfapi::handleForPath(localPath);
+    if (!handle) {
+        qCWarning(lcCfApi) << "Couldn't find pin state for non existing file" << localPath;
         return {};
     }
 
-    switch (info->PinState) {
-    case CF_PIN_STATE_UNSPECIFIED:
-        return PinState::Unspecified;
-    case CF_PIN_STATE_PINNED:
-        return PinState::AlwaysLocal;
-    case CF_PIN_STATE_UNPINNED:
-        return PinState::OnlineOnly;
-    case CF_PIN_STATE_INHERIT:
-        return PinState::Inherited;
-    default:
-        Q_UNREACHABLE();
+    const auto info = cfapi::findPlaceholderInfo(handle);
+    if (!info) {
+        qCWarning(lcCfApi) << "Couldn't find pin state for regular non-placeholder file" << localPath;
         return {};
     }
+
+    return info.pinState();
 }
 
 Vfs::AvailabilityResult VfsCfApi::availability(const QString &folderPath)