Split the CfAPI lower level code in a wrapper

Signed-off-by: Kevin Ottens <kevin.ottens@nextcloud.com>
This commit is contained in:
Kevin Ottens 2020-12-23 17:38:05 +01:00
parent 423780bf79
commit 7668c521c1
No known key found for this signature in database
GPG key ID: 074BBBCB8DECC9E2
4 changed files with 538 additions and 263 deletions

View 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()

View file

@ -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;
}
}

View file

@ -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

View file

@ -17,6 +17,7 @@
#include <QDir>
#include <QFile>
#include "cfapiwrapper.h"
#include "syncfileitem.h"
#include "filesystem.h"
#include "common/syncjournaldb.h"
@ -26,192 +27,8 @@
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)