diff --git a/src/libsync/vfs/cfapi/cfapiwrapper.cpp b/src/libsync/vfs/cfapi/cfapiwrapper.cpp index 416264e043c76..f538c4c09dfd6 100644 --- a/src/libsync/vfs/cfapi/cfapiwrapper.cpp +++ b/src/libsync/vfs/cfapi/cfapiwrapper.cpp @@ -7,39 +7,38 @@ #include "config.h" -#include "common/utility.h" +#include "accessmanager.h" #include "common/filesystembase.h" +#include "common/utility.h" #include "hydrationjob.h" #include "theme.h" #include "vfs_cfapi.h" -#include "accessmanager.h" #include +#include #include -#include +#include #include #include -#include -#include #include #include +#include #include -#include -#include #include #include +#include #include +#include Q_LOGGING_CATEGORY(lcCfApiWrapper, "nextcloud.sync.vfs.cfapi.wrapper", QtInfoMsg) using namespace Qt::Literals::StringLiterals; -#define FIELD_SIZE( type, field ) ( sizeof( ( (type*)0 )->field ) ) -#define CF_SIZE_OF_OP_PARAM( field ) \ - ( FIELD_OFFSET( CF_OPERATION_PARAMETERS, field ) + \ - FIELD_SIZE( CF_OPERATION_PARAMETERS, field ) ) +#define FIELD_SIZE(type, field) (sizeof(((type *)0)->field)) +#define CF_SIZE_OF_OP_PARAM(field) (FIELD_OFFSET(CF_OPERATION_PARAMETERS, field) + FIELD_SIZE(CF_OPERATION_PARAMETERS, field)) -namespace { +namespace +{ constexpr auto syncRootFlagsFull = 34; constexpr auto syncRootFlagsNoCfApiContextMenu = 2; @@ -50,7 +49,6 @@ constexpr auto forbiddenLeadingCharacterInPath = "#"; } - QString createErrorMessageForPlaceholderUpdateAndCreate(const QString &path, const QString &originalErrorMessage) { const auto pathFromNativeSeparators = QDir::fromNativeSeparators(path); @@ -91,11 +89,16 @@ OCC::PinState cfPinStateToPinState(CF_PIN_STATE state) } } -void cfApiSendTransferInfo(const CF_CONNECTION_KEY &connectionKey, const CF_TRANSFER_KEY &transferKey, NTSTATUS status, void *buffer, qint64 offset, qint64 currentBlockLength, qint64 totalLength) +void cfApiSendTransferInfo(const CF_CONNECTION_KEY &connectionKey, + const CF_TRANSFER_KEY &transferKey, + NTSTATUS status, + void *buffer, + qint64 offset, + qint64 currentBlockLength, + qint64 totalLength) { - - CF_OPERATION_INFO opInfo = { 0 }; - CF_OPERATION_PARAMETERS opParams = { 0 }; + CF_OPERATION_INFO opInfo = {0}; + CF_OPERATION_PARAMETERS opParams = {0}; opInfo.StructSize = sizeof(opInfo); opInfo.Type = CF_OPERATION_TYPE_TRANSFER_DATA; @@ -109,7 +112,8 @@ void cfApiSendTransferInfo(const CF_CONNECTION_KEY &connectionKey, const CF_TRAN const qint64 cfExecuteresult = CfExecute(&opInfo, &opParams); if (cfExecuteresult != S_OK) { - qCCritical(lcCfApiWrapper) << "Couldn't send transfer info" << QString::number(transferKey.QuadPart, 16) << ":" << cfExecuteresult << QString::fromWCharArray(_com_error(cfExecuteresult).ErrorMessage()); + qCCritical(lcCfApiWrapper) << "Couldn't send transfer info" << QString::number(transferKey.QuadPart, 16) << ":" << cfExecuteresult + << QString::fromWCharArray(_com_error(cfExecuteresult).ErrorMessage()); } const auto isDownloadFinished = ((offset + currentBlockLength) == totalLength); @@ -124,10 +128,11 @@ void cfApiSendTransferInfo(const CF_CONNECTION_KEY &connectionKey, const CF_TRAN LARGE_INTEGER progressCompleted; progressCompleted.QuadPart = offset; - const qint64 cfReportProgressresult = CfReportProviderProgress(connectionKey, transferKey, progressTotal, progressCompleted); + const qint64 cfReportProgressresult = CfReportProviderProgress(connectionKey, transferKey, progressTotal, progressCompleted); if (cfReportProgressresult != S_OK) { - qCCritical(lcCfApiWrapper) << "Couldn't report provider progress" << QString::number(transferKey.QuadPart, 16) << ":" << cfReportProgressresult << QString::fromWCharArray(_com_error(cfReportProgressresult).ErrorMessage()); + qCCritical(lcCfApiWrapper) << "Couldn't report provider progress" << QString::number(transferKey.QuadPart, 16) << ":" << cfReportProgressresult + << QString::fromWCharArray(_com_error(cfReportProgressresult).ErrorMessage()); } } @@ -139,8 +144,8 @@ void cfApiSendPlaceholdersTransferInfo(const CF_CONNECTION_KEY &connectionKey, qint64 totalPlaceholdersCount, const QString &serverPath) { - CF_OPERATION_INFO opInfo = { 0 }; - CF_OPERATION_PARAMETERS opParams = { 0 }; + CF_OPERATION_INFO opInfo = {0}; + CF_OPERATION_PARAMETERS opParams = {0}; const auto newPlaceholders = std::make_unique(newEntries.size()); @@ -148,9 +153,7 @@ void cfApiSendPlaceholdersTransferInfo(const CF_CONNECTION_KEY &connectionKey, const auto &entryInfo = newEntries[i]; auto &newPlaceholder = newPlaceholders[i]; - qCInfo(lcCfApiWrapper()) << entryInfo.name - << "fileId:" << entryInfo.parsedProperties.fileId - << "fileSize:" << entryInfo.parsedProperties.size + qCInfo(lcCfApiWrapper()) << entryInfo.name << "fileId:" << entryInfo.parsedProperties.fileId << "fileSize:" << entryInfo.parsedProperties.size << "fileMtime:" << entryInfo.parsedProperties.modtime << "fileResourceType:" << (entryInfo.parsedProperties.isDirectory ? "folder" : "file"); @@ -198,9 +201,8 @@ void cfApiSendPlaceholdersTransferInfo(const CF_CONNECTION_KEY &connectionKey, const qint64 cfExecuteresult = CfExecute(&opInfo, &opParams); if (cfExecuteresult != S_OK) { qCCritical(lcCfApiWrapper).nospace() << "Couldn't send transfer info, consider placeholders to be invalid" - << " transferKey=" << QString::number(transferKey.QuadPart, 16) - << " cfExecuteresult=" << cfExecuteresult - << " errorMessage="<< QString::fromWCharArray(_com_error(cfExecuteresult).ErrorMessage()); + << " transferKey=" << QString::number(transferKey.QuadPart, 16) << " cfExecuteresult=" << cfExecuteresult + << " errorMessage=" << QString::fromWCharArray(_com_error(cfExecuteresult).ErrorMessage()); for (auto &newEntry : newEntries) { newEntry.creationStatus = OCC::VirtualItemCreationStatus::Error; @@ -251,7 +253,8 @@ void CALLBACK cfApiFetchDataCallback(const CF_CALLBACK_INFO *callbackInfo, const const auto requestId = QString::number(callbackInfo->TransferKey.QuadPart, 16); if (!callbackInfo->ProcessInfo) { - qCCritical(lcCfApiWrapper) << "Callback parameters did not contain required process info required for the implicit hydration check, aborting" << path << requestId; + qCCritical(lcCfApiWrapper) << "Callback parameters did not contain required process info required for the implicit hydration check, aborting" << path + << requestId; sendTransferError(); return; } @@ -287,7 +290,12 @@ void CALLBACK cfApiFetchDataCallback(const CF_CALLBACK_INFO *callbackInfo, const qCDebug(lcCfApiWrapper) << "Request hydration for" << path << requestId; - const auto invokeResult = QMetaObject::invokeMethod(vfs, [=] { vfs->requestHydration(requestId, path); }, Qt::QueuedConnection); + const auto invokeResult = QMetaObject::invokeMethod( + vfs, + [=] { + vfs->requestHydration(requestId, path); + }, + Qt::QueuedConnection); if (!invokeResult) { qCCritical(lcCfApiWrapper) << "Failed to trigger hydration for" << path << requestId; QObject::disconnect(vfs, nullptr, &loop, nullptr); // Ensure we properly cancel hydration on server errors @@ -323,8 +331,7 @@ void CALLBACK cfApiFetchDataCallback(const CF_CALLBACK_INFO *callbackInfo, const signalSocket.connectToServer(signalSocketName); const auto cancellationSocketConnectResult = signalSocket.waitForConnected(); if (!cancellationSocketConnectResult) { - qCWarning(lcCfApiWrapper) << "Couldn't connect the socket" << signalSocketName - << signalSocket.error() << signalSocket.errorString(); + qCWarning(lcCfApiWrapper) << "Couldn't connect the socket" << signalSocketName << signalSocket.error() << signalSocket.errorString(); sendTransferError(); return; } @@ -390,7 +397,11 @@ void CALLBACK cfApiFetchDataCallback(const CF_CALLBACK_INFO *callbackInfo, const int hydrationJobResult = OCC::HydrationJob::Status::Error; const auto invokeFinalizeResult = QMetaObject::invokeMethod( - vfs, [&hydrationJobResult, vfs, requestId] { return vfs->finalizeHydrationJob(requestId); }, Qt::BlockingQueuedConnection, + vfs, + [&hydrationJobResult, vfs, requestId] { + return vfs->finalizeHydrationJob(requestId); + }, + Qt::BlockingQueuedConnection, &hydrationJobResult); if (!invokeFinalizeResult) { qCritical(lcCfApiWrapper) << "Failed to finalize hydration job for" << path << requestId; @@ -406,10 +417,8 @@ enum class CfApiUpdateMetadataType { AllMetadata, }; -OCC::Result updatePlaceholderState(const QString &path, - const OCC::SyncFileItem &item, - const QString &replacesPath, - CfApiUpdateMetadataType updateType) +OCC::Result +updatePlaceholderState(const QString &path, const OCC::SyncFileItem &item, const QString &replacesPath, CfApiUpdateMetadataType updateType) { const time_t modtime = item._modtime; const qint64 size = item._size; @@ -419,10 +428,9 @@ OCC::Result updatePlaceholderStat return {QString{"Could not update metadata due to invalid modification time for %1: %2"}.arg(path).arg(modtime)}; } - const auto info = replacesPath.isEmpty() ? OCC::CfApiWrapper::findPlaceholderInfo(path) - : OCC::CfApiWrapper::findPlaceholderInfo(replacesPath); + const auto info = replacesPath.isEmpty() ? OCC::CfApiWrapper::findPlaceholderInfo(path) : OCC::CfApiWrapper::findPlaceholderInfo(replacesPath); if (!info) { - return { "Can't update non existing placeholder info" }; + return {"Can't update non existing placeholder info"}; } const auto previousPinState = cfPinStateToPinState(info->PinState); @@ -435,15 +443,23 @@ OCC::Result updatePlaceholderStat OCC::Utility::UnixTimeToLargeIntegerFiletime(modtime, &metadata.BasicInfo.ChangeTime); qCInfo(lcCfApiWrapper) << "updatePlaceholderState" << path << modtime; - const auto updateFlags = item.isDirectory() && !item.isEncrypted() ? CF_UPDATE_FLAG_MARK_IN_SYNC | CF_UPDATE_FLAG_ENABLE_ON_DEMAND_POPULATION : CF_UPDATE_FLAG_MARK_IN_SYNC; - - const auto result = CfUpdatePlaceholder(OCC::CfApiWrapper::handleForPath(path).get(), updateType == CfApiUpdateMetadataType::AllMetadata ? &metadata : nullptr, - fileId.data(), static_cast(fileId.size()), - nullptr, 0, updateFlags, nullptr, nullptr); - - // Pin state tends to be lost on updates, so restore it every time + const auto updateFlags = item.isDirectory() && item._type == CSyncEnums::ItemType::ItemTypeVirtualDirectory && !item.isEncrypted() + ? CF_UPDATE_FLAG_MARK_IN_SYNC | CF_UPDATE_FLAG_ENABLE_ON_DEMAND_POPULATION + : CF_UPDATE_FLAG_MARK_IN_SYNC; + + const auto result = CfUpdatePlaceholder(OCC::CfApiWrapper::handleForPath(path).get(), + updateType == CfApiUpdateMetadataType::AllMetadata ? &metadata : nullptr, + fileId.data(), + static_cast(fileId.size()), + nullptr, + 0, + updateFlags, + nullptr, + nullptr); + + // Pin state tends to be lost on updates, so restore it every time if (!setPinState(path, previousPinState, OCC::CfApiWrapper::NoRecurse)) { - return { "Couldn't restore pin state" }; + return {"Couldn't restore pin state"}; } return OCC::Vfs::ConvertToPlaceholderResult::Ok; @@ -457,7 +473,8 @@ void CALLBACK cfApiCancelFetchData(const CF_CALLBACK_INFO *callbackInfo, const C qCInfo(lcCfApiWrapper) << "Desktop client process id:" << QCoreApplication::applicationPid(); if (callbackInfo->ProcessInfo) { qCInfo(lcCfApiWrapper) << "Cancel fetch data requested by process id:" << callbackInfo->ProcessInfo->ProcessId; - qCInfo(lcCfApiWrapper) << "Cancel fetch data requested by application id:" << QString(QString::fromWCharArray(callbackInfo->ProcessInfo->ApplicationId)); + qCInfo(lcCfApiWrapper) << "Cancel fetch data requested by application id:" + << QString(QString::fromWCharArray(callbackInfo->ProcessInfo->ApplicationId)); } auto vfs = reinterpret_cast(callbackInfo->CallbackContext); @@ -465,7 +482,11 @@ void CALLBACK cfApiCancelFetchData(const CF_CALLBACK_INFO *callbackInfo, const C const auto requestId = QString::number(callbackInfo->TransferKey.QuadPart, 16); const auto invokeResult = QMetaObject::invokeMethod( - vfs, [=] { vfs->cancelHydration(requestId, path); }, Qt::QueuedConnection); + vfs, + [=] { + vfs->cancelHydration(requestId, path); + }, + Qt::QueuedConnection); if (!invokeResult) { qCritical(lcCfApiWrapper) << "Failed to cancel hydration for" << path << requestId; } @@ -515,7 +536,8 @@ void CALLBACK cfApiCancelFetchPlaceHolders(const CF_CALLBACK_INFO *callbackInfo, qCInfo(lcCfApiWrapper) << "Desktop client process id:" << QCoreApplication::applicationPid(); if (callbackInfo->ProcessInfo) { qCInfo(lcCfApiWrapper) << "Cancel fetch placeholder requested by process id:" << callbackInfo->ProcessInfo->ProcessId; - qCInfo(lcCfApiWrapper) << "Cancel fetch placeholder requested by application id:" << QString(QString::fromWCharArray(callbackInfo->ProcessInfo->ApplicationId)); + qCInfo(lcCfApiWrapper) << "Cancel fetch placeholder requested by application id:" + << QString(QString::fromWCharArray(callbackInfo->ProcessInfo->ApplicationId)); } } @@ -529,18 +551,13 @@ void CALLBACK cfApiFetchPlaceHolders(const CF_CALLBACK_INFO *callbackInfo, const qDebug(lcCfApiWrapper) << "Fetch placeholders requested by application id:" << QString(QString::fromWCharArray(callbackInfo->ProcessInfo->ApplicationId)); qDebug(lcCfApiWrapper) << "Fetch placeholders requested for path" << path; if (callbackParameters->FetchPlaceholders.Pattern) { - qDebug(lcCfApiWrapper) << "Fetch placeholders requested with pattern:" << QString(QString::fromWCharArray(callbackParameters->FetchPlaceholders.Pattern)); + qDebug(lcCfApiWrapper) << "Fetch placeholders requested with pattern:" + << QString(QString::fromWCharArray(callbackParameters->FetchPlaceholders.Pattern)); } const auto sendTransferError = [=] { auto emptyList = QList{}; - cfApiSendPlaceholdersTransferInfo(callbackInfo->ConnectionKey, - callbackInfo->TransferKey, - STATUS_UNSUCCESSFUL, - emptyList, - 0, - 0, - {}); + cfApiSendPlaceholdersTransferInfo(callbackInfo->ConnectionKey, callbackInfo->TransferKey, STATUS_UNSUCCESSFUL, emptyList, 0, 0, {}); }; const auto sendTransferInfo = [=](QList &newEntries, const QString &serverPath) { @@ -567,8 +584,7 @@ void CALLBACK cfApiFetchPlaceHolders(const CF_CALLBACK_INFO *callbackInfo, const auto rootPath = QFileInfo{vfs->params().filesystemPath}.canonicalFilePath(); if (!pathString.startsWith(rootPath)) { - qCCritical(lcCfApiWrapper) << "wrong path" << pathString << rootPath, - sendTransferError(); + qCCritical(lcCfApiWrapper) << "wrong path" << pathString << rootPath, sendTransferError(); return; } const auto remoteSyncRootPath = vfs->params().remotePath; // with leading slash @@ -586,25 +602,27 @@ void CALLBACK cfApiFetchPlaceHolders(const CF_CALLBACK_INFO *callbackInfo, const QList newEntries; - QObject::connect(&lsPropFutureWatcher, &QFutureWatcher::finished, - &localEventLoop, [&localEventLoop] () { - qCInfo(lcCfApiWrapper()) << "ls prop finished"; - localEventLoop.quit(); - }); + QObject::connect(&lsPropFutureWatcher, &QFutureWatcher::finished, &localEventLoop, [&localEventLoop]() { + qCInfo(lcCfApiWrapper()) << "ls prop finished"; + localEventLoop.quit(); + }); - QObject::connect(&lsPropFutureWatcher, &QFutureWatcher::resultReadyAt, - &localEventLoop, [&newEntries, &lsPropFutureWatcher] (int resultIndex) { - qCInfo(lcCfApiWrapper()) << "ls prop result at index" << resultIndex; - const auto &newResultEntries = lsPropFutureWatcher.resultAt(resultIndex); - newEntries.append(newResultEntries); - }); + QObject::connect(&lsPropFutureWatcher, &QFutureWatcher::resultReadyAt, &localEventLoop, [&newEntries, &lsPropFutureWatcher](int resultIndex) { + qCInfo(lcCfApiWrapper()) << "ls prop result at index" << resultIndex; + const auto &newResultEntries = lsPropFutureWatcher.resultAt(resultIndex); + newEntries.append(newResultEntries); + }); - QObject::connect(&lsPropFutureWatcher, &QFutureWatcher::started, - &localEventLoop, [] () { - qCInfo(lcCfApiWrapper()) << "ls prop started"; - }); + QObject::connect(&lsPropFutureWatcher, &QFutureWatcher::started, &localEventLoop, []() { + qCInfo(lcCfApiWrapper()) << "ls prop started"; + }); - QMetaObject::invokeMethod(vfs->params().account.data(), &OCC::Account::listRemoteFolder, &lsPropPromise, remoteSyncRootPath, serverPath, vfs->params().journal); + QMetaObject::invokeMethod(vfs->params().account.data(), + &OCC::Account::listRemoteFolder, + &lsPropPromise, + remoteSyncRootPath, + serverPath, + vfs->params().journal); qCInfo(lcCfApiWrapper()) << "ls prop requested" << path << serverPath; @@ -615,10 +633,13 @@ void CALLBACK cfApiFetchPlaceHolders(const CF_CALLBACK_INFO *callbackInfo, const sendTransferInfo(newEntries, serverPath); auto newPlaceholdersResult = 0; - const auto invokeFinalizeResult = QMetaObject::invokeMethod(vfs, - [&newPlaceholdersResult, vfs, &newEntries, &relativeLocalPath] () -> int { return vfs->finalizeNewPlaceholders(newEntries, relativeLocalPath); }, - Qt::BlockingQueuedConnection, - qReturnArg(newPlaceholdersResult)); + const auto invokeFinalizeResult = QMetaObject::invokeMethod( + vfs, + [&newPlaceholdersResult, vfs, &newEntries, &relativeLocalPath]() -> int { + return vfs->finalizeNewPlaceholders(newEntries, relativeLocalPath); + }, + Qt::BlockingQueuedConnection, + qReturnArg(newPlaceholdersResult)); if (!invokeFinalizeResult) { qCritical(lcCfApiWrapper) << "Failed to finalize hydration job for" << path << requestId; sendTransferError(); @@ -647,17 +668,14 @@ void CALLBACK cfApiNotifyFileCloseCompletion(const CF_CALLBACK_INFO *callbackInf } } -CF_CALLBACK_REGISTRATION cfApiCallbacks[] = { - { CF_CALLBACK_TYPE_FETCH_DATA, cfApiFetchDataCallback }, - { CF_CALLBACK_TYPE_CANCEL_FETCH_DATA, cfApiCancelFetchData }, - { CF_CALLBACK_TYPE_NOTIFY_FILE_OPEN_COMPLETION, cfApiNotifyFileOpenCompletion }, - { CF_CALLBACK_TYPE_NOTIFY_FILE_CLOSE_COMPLETION, cfApiNotifyFileCloseCompletion }, - { CF_CALLBACK_TYPE_VALIDATE_DATA, cfApiValidateData }, - { CF_CALLBACK_TYPE_FETCH_PLACEHOLDERS, cfApiFetchPlaceHolders }, - { CF_CALLBACK_TYPE_CANCEL_FETCH_PLACEHOLDERS, cfApiCancelFetchPlaceHolders }, - CF_CALLBACK_REGISTRATION_END -}; - +CF_CALLBACK_REGISTRATION cfApiCallbacks[] = {{CF_CALLBACK_TYPE_FETCH_DATA, cfApiFetchDataCallback}, + {CF_CALLBACK_TYPE_CANCEL_FETCH_DATA, cfApiCancelFetchData}, + {CF_CALLBACK_TYPE_NOTIFY_FILE_OPEN_COMPLETION, cfApiNotifyFileOpenCompletion}, + {CF_CALLBACK_TYPE_NOTIFY_FILE_CLOSE_COMPLETION, cfApiNotifyFileCloseCompletion}, + {CF_CALLBACK_TYPE_VALIDATE_DATA, cfApiValidateData}, + {CF_CALLBACK_TYPE_FETCH_PLACEHOLDERS, cfApiFetchPlaceHolders}, + {CF_CALLBACK_TYPE_CANCEL_FETCH_PLACEHOLDERS, cfApiCancelFetchPlaceHolders}, + CF_CALLBACK_REGISTRATION_END}; std::wstring pathForHandle(const OCC::CfApiWrapper::FileHandle &handle) { @@ -701,9 +719,8 @@ CF_SET_PIN_FLAGS pinRecurseModeToCfSetPinFlags(OCC::CfApiWrapper::SetPinRecurseM } } - OCC::CfApiWrapper::FileHandle::FileHandle() - : _data(nullptr, [](void *) {}) + : _data(nullptr, [](void *) { }) { } @@ -754,7 +771,7 @@ std::unique_ptr getCurrentTokenInformation() std::unique_ptr tokenInfo; - tokenInfo.reset(reinterpret_cast(new char[tokenInfoSize])); + tokenInfo.reset(reinterpret_cast(new char[tokenInfoSize])); if (!::GetTokenInformation(tokenHandle, TokenUser, tokenInfo.get(), tokenInfoSize, &tokenInfoSize)) { qCCritical(lcCfApiWrapper) << "GetTokenInformation failed with error" << lastError; return {}; @@ -772,7 +789,12 @@ QString retrieveWindowsSid() return {}; } -bool createSyncRootRegistryKeys(const QString &providerName, const QString &folderAlias, const QString &navigationPaneClsid, const QString &displayName, const QString &accountDisplayName, const QString &syncRootPath) +bool createSyncRootRegistryKeys(const QString &providerName, + const QString &folderAlias, + const QString &navigationPaneClsid, + const QString &displayName, + const QString &accountDisplayName, + const QString &syncRootPath) { // We must set specific Registry keys to make the progress bar refresh correctly and also add status icons into Windows Explorer // More about this here: https://docs.microsoft.com/en-us/windows/win32/shell/integrate-cloud-storage @@ -783,8 +805,10 @@ bool createSyncRootRegistryKeys(const QString &providerName, const QString &fold return false; } - // syncRootId should be: [storage provider ID]![Windows SID]![Account ID]![FolderAlias] (FolderAlias is a custom part added here to be able to register multiple sync folders for the same account) - // folder registry keys go like: Nextcloud!S-1-5-21-2096452760-2617351404-2281157308-1001!user@nextcloud.lan:8080!0, Nextcloud!S-1-5-21-2096452760-2617351404-2281157308-1001!user@nextcloud.lan:8080!1, etc. for each sync folder + // syncRootId should be: [storage provider ID]![Windows SID]![Account ID]![FolderAlias] (FolderAlias is a custom part added here to be able to register + // multiple sync folders for the same account) folder registry keys go like: + // Nextcloud!S-1-5-21-2096452760-2617351404-2281157308-1001!user@nextcloud.lan:8080!0, + // Nextcloud!S-1-5-21-2096452760-2617351404-2281157308-1001!user@nextcloud.lan:8080!1, etc. for each sync folder const auto syncRootId = QStringLiteral("%1!%2!%3!%4").arg(providerName).arg(windowsSid).arg(accountDisplayName).arg(folderAlias); const QString providerSyncRootIdRegistryKey = syncRootManagerRegKey + QStringLiteral("\\") + syncRootId; const QString providerSyncRootIdUserSyncRootsRegistryKey = providerSyncRootIdRegistryKey + QStringLiteral(R"(\UserSyncRoots\)"); @@ -801,26 +825,30 @@ bool createSyncRootRegistryKeys(const QString &providerName, const QString &fold qCDebug(lcCfApiWrapper) << "syncRootPath" << syncRootPath << " will use DisplayNameResource" << displayName; const QVector registryKeysToSet = { - { providerSyncRootIdRegistryKey, QStringLiteral("Flags"), REG_DWORD, flags }, - { providerSyncRootIdRegistryKey, QStringLiteral("DisplayNameResource"), REG_EXPAND_SZ, displayName }, - { providerSyncRootIdRegistryKey, QStringLiteral("IconResource"), REG_EXPAND_SZ, QString(QDir::toNativeSeparators(qApp->applicationFilePath()) + QStringLiteral(",0")) }, - { providerSyncRootIdUserSyncRootsRegistryKey, windowsSid, REG_SZ, syncRootPath}, - { providerSyncRootIdRegistryKey, QStringLiteral("CustomStateHandler"), REG_SZ, CFAPI_SHELLEXT_CUSTOM_STATE_HANDLER_CLASS_ID_REG}, - { providerSyncRootIdRegistryKey, QStringLiteral("ThumbnailProvider"), REG_SZ, CFAPI_SHELLEXT_THUMBNAIL_HANDLER_CLASS_ID_REG}, - { providerSyncRootIdRegistryKey, QStringLiteral("NamespaceCLSID"), REG_SZ, QString(navigationPaneClsid)} - }; + {providerSyncRootIdRegistryKey, QStringLiteral("Flags"), REG_DWORD, flags}, + {providerSyncRootIdRegistryKey, QStringLiteral("DisplayNameResource"), REG_EXPAND_SZ, displayName}, + {providerSyncRootIdRegistryKey, + QStringLiteral("IconResource"), + REG_EXPAND_SZ, + QString(QDir::toNativeSeparators(qApp->applicationFilePath()) + QStringLiteral(",0"))}, + {providerSyncRootIdUserSyncRootsRegistryKey, windowsSid, REG_SZ, syncRootPath}, + {providerSyncRootIdRegistryKey, QStringLiteral("CustomStateHandler"), REG_SZ, CFAPI_SHELLEXT_CUSTOM_STATE_HANDLER_CLASS_ID_REG}, + {providerSyncRootIdRegistryKey, QStringLiteral("ThumbnailProvider"), REG_SZ, CFAPI_SHELLEXT_THUMBNAIL_HANDLER_CLASS_ID_REG}, + {providerSyncRootIdRegistryKey, QStringLiteral("NamespaceCLSID"), REG_SZ, QString(navigationPaneClsid)}}; for (const auto ®istryKeyToSet : std::as_const(registryKeysToSet)) { - if (!OCC::Utility::registrySetKeyValue(HKEY_LOCAL_MACHINE, registryKeyToSet.subKey, registryKeyToSet.valueName, registryKeyToSet.type, registryKeyToSet.value)) { + if (!OCC::Utility::registrySetKeyValue(HKEY_LOCAL_MACHINE, + registryKeyToSet.subKey, + registryKeyToSet.valueName, + registryKeyToSet.type, + registryKeyToSet.value)) { qCWarning(lcCfApiWrapper) << "Failed to set Registry keys for shell integration. Progress bar will not work."; const auto deleteKeyResult = OCC::Utility::registryDeleteKeyTree(HKEY_LOCAL_MACHINE, providerSyncRootIdRegistryKey); Q_ASSERT(!deleteKeyResult); return false; } - qCDebug(lcCfApiWrapper) << "Registering keys - subKey:" << registryKeyToSet.subKey - << " - valueName:" << registryKeyToSet.valueName - << " - type:" << registryKeyToSet.type - << " - value:" << registryKeyToSet.value.toString(); + qCDebug(lcCfApiWrapper) << "Registering keys - subKey:" << registryKeyToSet.subKey << " - valueName:" << registryKeyToSet.valueName + << " - type:" << registryKeyToSet.type << " - value:" << registryKeyToSet.value.toString(); } qCInfo(lcCfApiWrapper) << "Successfully set Registry keys for shell integration at:" << providerSyncRootIdRegistryKey << ". Progress bar will work."; @@ -846,7 +874,8 @@ bool deleteSyncRootRegistryKey(const QString &syncRootPath, const QString &provi OCC::Utility::registryWalkSubKeys(HKEY_LOCAL_MACHINE, syncRootManagerRegKey, [&](HKEY, const QString &syncRootId) { // make sure we have matching syncRootId(providerName!windowsSid!accountDisplayName) if (syncRootId.startsWith(currentUserSyncRootIdPattern)) { - const QString syncRootIdUserSyncRootsRegistryKey = syncRootManagerRegKey + QStringLiteral("\\") + syncRootId + QStringLiteral(R"(\UserSyncRoots\)"); + const QString syncRootIdUserSyncRootsRegistryKey = + syncRootManagerRegKey + QStringLiteral("\\") + syncRootId + QStringLiteral(R"(\UserSyncRoots\)"); // check if there is a 'windowsSid' Registry value under \UserSyncRoots and it matches the sync folder path we are removing if (OCC::Utility::registryGetKeyValue(HKEY_LOCAL_MACHINE, syncRootIdUserSyncRootsRegistryKey, windowsSid).toString() == syncRootPath) { const QString syncRootIdToDelete = syncRootManagerRegKey + QStringLiteral("\\") + syncRootId; @@ -859,7 +888,13 @@ bool deleteSyncRootRegistryKey(const QString &syncRootPath, const QString &provi return true; } -OCC::Result OCC::CfApiWrapper::registerSyncRoot(const QString &path, const QString &providerName, const QString &providerVersion, const QString &folderAlias, const QString &navigationPaneClsid, const QString &displayName, const QString &accountDisplayName) +OCC::Result OCC::CfApiWrapper::registerSyncRoot(const QString &path, + const QString &providerName, + const QString &providerVersion, + const QString &folderAlias, + const QString &navigationPaneClsid, + const QString &displayName, + const QString &accountDisplayName) { // even if we fail to register our sync root with shell, we can still proceed with using the VFS const auto createRegistryKeyResult = createSyncRootRegistryKeys(providerName, folderAlias, navigationPaneClsid, displayName, accountDisplayName, path); @@ -941,12 +976,16 @@ OCC::Result OCC::CfApiWrapper::connectSyncRoot(const CF_CONNECTION_KEY key; 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 | CF_CONNECT_FLAG_BLOCK_SELF_IMPLICIT_HYDRATION, &key); + CfConnectSyncRoot(p.data(), + cfApiCallbacks, + context, + CF_CONNECT_FLAG_REQUIRE_PROCESS_INFO | CF_CONNECT_FLAG_REQUIRE_FULL_FILE_PATH | CF_CONNECT_FLAG_BLOCK_SELF_IMPLICIT_HYDRATION, + &key); Q_ASSERT(result == S_OK); if (result != S_OK) { return QString::fromWCharArray(_com_error(result).ErrorMessage()); } else { - return { std::move(key) }; + return {std::move(key)}; } } @@ -975,12 +1014,11 @@ bool OCC::CfApiWrapper::isAnySyncRoot(const QString &providerName, const QString if (Utility::registryKeyExists(HKEY_LOCAL_MACHINE, syncRootManagerRegKey)) { bool foundSyncRoots = false; - Utility::registryWalkSubKeys(HKEY_LOCAL_MACHINE, syncRootManagerRegKey, - [&foundSyncRoots, &syncRootPrefix](HKEY key, const QString &subKey) { - if (subKey.startsWith(syncRootPrefix)) { - foundSyncRoots = true; - } - }); + Utility::registryWalkSubKeys(HKEY_LOCAL_MACHINE, syncRootManagerRegKey, [&foundSyncRoots, &syncRootPrefix](HKEY key, const QString &subKey) { + if (subKey.startsWith(syncRootPrefix)) { + foundSyncRoots = true; + } + }); return foundSyncRoots; } @@ -1011,16 +1049,19 @@ OCC::CfApiWrapper::FileHandle OCC::CfApiWrapper::handleForPath(const QString &pa HANDLE handle = nullptr; const qint64 openResult = CfOpenFileWithOplock(path.toStdWString().data(), CF_OPEN_FILE_FLAG_NONE, &handle); if (openResult == S_OK) { - return {handle, [](HANDLE h) { CfCloseHandle(h); }}; + return {handle, [](HANDLE h) { + CfCloseHandle(h); + }}; } else { qCWarning(lcCfApiWrapper) << "Could not open handle for " << path << " result: " << QString::fromWCharArray(_com_error(openResult).ErrorMessage()); } } else if (FileSystem::isFile(path)) { const auto longpath = OCC::FileSystem::longWinPath(path); - const auto handle = CreateFile(longpath.toStdWString().data(), 0, 0, nullptr, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + const auto handle = CreateFile(longpath.toStdWString().data(), 0, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle != INVALID_HANDLE_VALUE) { - return {handle, [](HANDLE h) { CloseHandle(h); }}; + return {handle, [](HANDLE h) { + CloseHandle(h); + }}; } else { qCCritical(lcCfApiWrapper) << "Could not CreateFile for longpath:" << longpath << "with error:" << GetLastError(); } @@ -1063,8 +1104,9 @@ OCC::Result OCC::CfApiWrapper::se if (result == S_OK) { return OCC::Vfs::ConvertToPlaceholderResult::Ok; } else { - qCWarning(lcCfApiWrapper) << "Couldn't set pin state" << state << "for" << path << "with recurse mode" << mode << ":" << QString::fromWCharArray(_com_error(result).ErrorMessage()); - return { "Couldn't set pin state" }; + qCWarning(lcCfApiWrapper) << "Couldn't set pin state" << state << "for" << path << "with recurse mode" << mode << ":" + << QString::fromWCharArray(_com_error(result).ErrorMessage()); + return {"Couldn't set pin state"}; } } @@ -1101,14 +1143,14 @@ OCC::Result OCC::CfApiWrapper::createPlaceholderInfo(const QStrin 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 << ":" << QString::fromWCharArray(_com_error(result).ErrorMessage()); - return { "Couldn't create placeholder info" }; + 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, cfPinStateToPinState(state), NoRecurse)) { - return { "Couldn't set the default inherit pin state" }; + return {"Couldn't set the default inherit pin state"}; } return {}; @@ -1119,7 +1161,7 @@ OCC::Result OCC::CfApiWrapper::createPlaceholdersInfo(const QStri auto filteredItemsInfo = QList{}; filteredItemsInfo.reserve(itemsInfo.size()); - std::copy_if(itemsInfo.begin(), itemsInfo.end(), std::back_inserter(filteredItemsInfo), [] (const auto &onePlaceholderInfo) -> bool { + std::copy_if(itemsInfo.begin(), itemsInfo.end(), std::back_inserter(filteredItemsInfo), [](const auto &onePlaceholderInfo) -> bool { if (onePlaceholderInfo.modtime <= 0) { qCWarning(lcCfApiWrapper()) << "Skip invalid modtime file: " << onePlaceholderInfo.relativePath << "modtime:" << onePlaceholderInfo.modtime; return false; @@ -1130,7 +1172,7 @@ OCC::Result OCC::CfApiWrapper::createPlaceholdersInfo(const QStri const auto stdWStringBasePath = localBasePath.toStdWString(); auto cloudEntry = std::make_unique(filteredItemsInfo.size()); - for(auto itemIndice = 0; itemIndice < filteredItemsInfo.size(); ++itemIndice) { + for (auto itemIndice = 0; itemIndice < filteredItemsInfo.size(); ++itemIndice) { const auto &placeholderInfo = filteredItemsInfo[itemIndice]; cloudEntry[itemIndice].FileIdentity = placeholderInfo.fileId.data(); @@ -1153,36 +1195,44 @@ OCC::Result OCC::CfApiWrapper::createPlaceholdersInfo(const QStri } auto numberOfCreatedPlaceholders = 0ul; - const qint64 result = CfCreatePlaceholders(stdWStringBasePath.data(), cloudEntry.get(), filteredItemsInfo.size(), CF_CREATE_FLAG_NONE, &numberOfCreatedPlaceholders); + const qint64 result = + CfCreatePlaceholders(stdWStringBasePath.data(), cloudEntry.get(), filteredItemsInfo.size(), CF_CREATE_FLAG_NONE, &numberOfCreatedPlaceholders); if (result != S_OK) { - qCWarning(lcCfApiWrapper) << "Couldn't create placeholders info" << ":" << QString::fromWCharArray(_com_error(result).ErrorMessage()) << "number of placeholders created:" << numberOfCreatedPlaceholders; + qCWarning(lcCfApiWrapper) << "Couldn't create placeholders info" << ":" << QString::fromWCharArray(_com_error(result).ErrorMessage()) + << "number of placeholders created:" << numberOfCreatedPlaceholders; - for(auto itemIndice = 0; itemIndice < filteredItemsInfo.size(); ++itemIndice) { - qCWarning(lcCfApiWrapper) << QString::fromStdWString(cloudEntry[itemIndice].RelativeFileName) << QString::fromWCharArray(_com_error(cloudEntry[itemIndice].Result).ErrorMessage()); + for (auto itemIndice = 0; itemIndice < filteredItemsInfo.size(); ++itemIndice) { + qCWarning(lcCfApiWrapper) << QString::fromStdWString(cloudEntry[itemIndice].RelativeFileName) + << QString::fromWCharArray(_com_error(cloudEntry[itemIndice].Result).ErrorMessage()); } - return { "Couldn't create placeholder info" }; + return {"Couldn't create placeholder info"}; } - for(auto itemIndice = 0; itemIndice < filteredItemsInfo.size(); ++itemIndice) { + for (auto itemIndice = 0; itemIndice < filteredItemsInfo.size(); ++itemIndice) { const auto &placeholderInfo = filteredItemsInfo[itemIndice]; - const auto parentInfo = findPlaceholderInfo(QDir::toNativeSeparators(QFileInfo(localBasePath + QDir::separator() + placeholderInfo.relativePath).absolutePath())); + const auto parentInfo = + findPlaceholderInfo(QDir::toNativeSeparators(QFileInfo(localBasePath + QDir::separator() + placeholderInfo.relativePath).absolutePath())); const auto state = parentInfo && parentInfo->PinState == CF_PIN_STATE_UNPINNED ? CF_PIN_STATE_UNPINNED : CF_PIN_STATE_INHERIT; - if (!setPinState(QDir::toNativeSeparators(QFileInfo(localBasePath + QDir::separator() + placeholderInfo.relativePath).absoluteFilePath()), cfPinStateToPinState(state), NoRecurse)) { - return { "Couldn't set the default inherit pin state" }; + if (!setPinState(QDir::toNativeSeparators(QFileInfo(localBasePath + QDir::separator() + placeholderInfo.relativePath).absoluteFilePath()), + cfPinStateToPinState(state), + NoRecurse)) { + return {"Couldn't set the default inherit pin state"}; } } return {}; } -OCC::Result OCC::CfApiWrapper::updatePlaceholderInfo(const QString &path, const SyncFileItem &item, const QString &replacesPath) +OCC::Result +OCC::CfApiWrapper::updatePlaceholderInfo(const QString &path, const SyncFileItem &item, const QString &replacesPath) { return updatePlaceholderState(path, item, replacesPath, CfApiUpdateMetadataType::AllMetadata); } -OCC::Result OCC::CfApiWrapper::dehydratePlaceholder(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId) +OCC::Result +OCC::CfApiWrapper::dehydratePlaceholder(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId) { if (modtime <= 0) { return {QString{"Could not update metadata due to invalid modification time for %1: %2"}.arg(path).arg(modtime)}; @@ -1195,16 +1245,27 @@ OCC::Result OCC::CfApiWrapper::de CF_FILE_RANGE dehydrationRange = {}; dehydrationRange.Length.QuadPart = size; - const qint64 result = CfUpdatePlaceholder(handleForPath(path).get(), nullptr, fileId.data(), static_cast(fileId.size()), &dehydrationRange, 1, - CF_UPDATE_FLAG_MARK_IN_SYNC | CF_UPDATE_FLAG_DEHYDRATE, nullptr, nullptr); + const qint64 result = CfUpdatePlaceholder(handleForPath(path).get(), + nullptr, + fileId.data(), + static_cast(fileId.size()), + &dehydrationRange, + 1, + CF_UPDATE_FLAG_MARK_IN_SYNC | CF_UPDATE_FLAG_DEHYDRATE, + nullptr, + nullptr); if (result != S_OK) { const auto errorMessage = createErrorMessageForPlaceholderUpdateAndCreate(path, "Couldn't update placeholder info"); qCWarning(lcCfApiWrapper) << errorMessage << path << ":" << QString::fromWCharArray(_com_error(result).ErrorMessage()); return errorMessage; } } else { - const qint64 result = CfConvertToPlaceholder(handleForPath(path).get(), fileId.data(), static_cast(fileId.size()), - CF_CONVERT_FLAG_MARK_IN_SYNC | CF_CONVERT_FLAG_DEHYDRATE, nullptr, nullptr); + const qint64 result = CfConvertToPlaceholder(handleForPath(path).get(), + fileId.data(), + static_cast(fileId.size()), + CF_CONVERT_FLAG_MARK_IN_SYNC | CF_CONVERT_FLAG_DEHYDRATE, + nullptr, + nullptr); if (result != S_OK) { const auto errorMessage = createErrorMessageForPlaceholderUpdateAndCreate(path, "Couldn't convert to placeholder"); @@ -1216,14 +1277,18 @@ OCC::Result OCC::CfApiWrapper::de return OCC::Vfs::ConvertToPlaceholderResult::Ok; } -OCC::Result OCC::CfApiWrapper::convertToPlaceholder(const QString &path, const SyncFileItem &item, const QString &replacesPath) +OCC::Result +OCC::CfApiWrapper::convertToPlaceholder(const QString &path, const SyncFileItem &item, const QString &replacesPath) { const QByteArray &fileId = item._fileId; const auto fileIdentity = QString::fromUtf8(fileId).toStdWString(); const auto fileIdentitySize = (fileIdentity.length() + 1) * sizeof(wchar_t); - const auto createPlaceholderFlags = CF_CONVERT_FLAG_MARK_IN_SYNC | (item.isDirectory() ? (item._type == ItemType::ItemTypeVirtualDirectory ? CF_CONVERT_FLAG_ENABLE_ON_DEMAND_POPULATION : CF_CONVERT_FLAG_ALWAYS_FULL) : CF_CONVERT_FLAG_MARK_IN_SYNC); + const auto createPlaceholderFlags = CF_CONVERT_FLAG_MARK_IN_SYNC + | (item.isDirectory() ? (item._type == ItemType::ItemTypeVirtualDirectory ? CF_CONVERT_FLAG_ENABLE_ON_DEMAND_POPULATION : CF_CONVERT_FLAG_ALWAYS_FULL) + : CF_CONVERT_FLAG_MARK_IN_SYNC); - const auto result = CfConvertToPlaceholder(handleForPath(path).get(), fileIdentity.data(), sizeToDWORD(fileIdentitySize), createPlaceholderFlags, nullptr, nullptr); + const auto result = + CfConvertToPlaceholder(handleForPath(path).get(), fileIdentity.data(), sizeToDWORD(fileIdentitySize), createPlaceholderFlags, nullptr, nullptr); Q_ASSERT(result == S_OK); if (result != S_OK) { const auto errorMessage = createErrorMessageForPlaceholderUpdateAndCreate(path, "Couldn't convert to placeholder"); @@ -1244,7 +1309,8 @@ OCC::Result OCC::CfApiWrapper::co return stateResult; } -OCC::Result OCC::CfApiWrapper::updatePlaceholderMarkInSync(const QString &path, const SyncFileItem &item, const QString &replacesPath) +OCC::Result +OCC::CfApiWrapper::updatePlaceholderMarkInSync(const QString &path, const SyncFileItem &item, const QString &replacesPath) { return updatePlaceholderState(path, item, replacesPath, CfApiUpdateMetadataType::OnlyBasicMetadata); } diff --git a/src/libsync/vfs/cfapi/shellext/dllmain.cpp b/src/libsync/vfs/cfapi/shellext/dllmain.cpp index 8a4f0f5a1103b..24b51ef28f3db 100644 --- a/src/libsync/vfs/cfapi/shellext/dllmain.cpp +++ b/src/libsync/vfs/cfapi/shellext/dllmain.cpp @@ -6,7 +6,8 @@ #include "cfapishellintegrationclassfactory.h" #include "customstateprovider.h" #include "thumbnailprovider.h" -#include + +#include long dllReferenceCount = 0; long dllObjectsCount = 0; diff --git a/src/libsync/vfs/cfapi/vfs_cfapi.cpp b/src/libsync/vfs/cfapi/vfs_cfapi.cpp index 3872fb3ce49d5..327416662ae4e 100644 --- a/src/libsync/vfs/cfapi/vfs_cfapi.cpp +++ b/src/libsync/vfs/cfapi/vfs_cfapi.cpp @@ -9,22 +9,23 @@ #include #include "cfapiwrapper.h" -#include "hydrationjob.h" -#include "syncfileitem.h" -#include "filesystem.h" #include "common/filesystembase.h" #include "common/syncjournaldb.h" #include "config.h" +#include "filesystem.h" +#include "hydrationjob.h" +#include "syncfileitem.h" -#include #include #include +#include #include Q_LOGGING_CATEGORY(lcCfApi, "nextcloud.sync.vfs.cfapi", QtInfoMsg) -namespace cfapi { +namespace cfapi +{ using namespace OCC::CfApiWrapper; constexpr auto appIdRegKey = R"(Software\Classes\AppID\)"; @@ -35,11 +36,11 @@ bool registerShellExtension() { const QList> listExtensions = { {CFAPI_SHELLEXT_THUMBNAIL_HANDLER_DISPLAY_NAME, CFAPI_SHELLEXT_THUMBNAIL_HANDLER_CLASS_ID_REG}, - {CFAPI_SHELLEXT_CUSTOM_STATE_HANDLER_DISPLAY_NAME, CFAPI_SHELLEXT_CUSTOM_STATE_HANDLER_CLASS_ID_REG} - }; + {CFAPI_SHELLEXT_CUSTOM_STATE_HANDLER_DISPLAY_NAME, CFAPI_SHELLEXT_CUSTOM_STATE_HANDLER_CLASS_ID_REG}}; // assume CFAPI_SHELL_EXTENSIONS_LIB_NAME is always in the same folder as the main executable // assume CFAPI_SHELL_EXTENSIONS_LIB_NAME is always in the same folder as the main executable - const auto shellExtensionDllPath = QDir::toNativeSeparators(QString(QCoreApplication::applicationDirPath() + QStringLiteral("/") + CFAPI_SHELL_EXTENSIONS_LIB_NAME + QStringLiteral(".dll"))); + const auto shellExtensionDllPath = QDir::toNativeSeparators( + QString(QCoreApplication::applicationDirPath() + QStringLiteral("/") + CFAPI_SHELL_EXTENSIONS_LIB_NAME + QStringLiteral(".dll"))); if (!OCC::FileSystem::fileExists(shellExtensionDllPath)) { Q_ASSERT(false); qCWarning(lcCfApi) << "Register CfAPI shell extensions failed. Dll does not exist in " << QCoreApplication::applicationDirPath(); @@ -82,10 +83,7 @@ void unregisterShellExtensions() OCC::Utility::registryDeleteKeyTree(rootKey, appIdPath); } - const QStringList listExtensions = { - CFAPI_SHELLEXT_CUSTOM_STATE_HANDLER_CLASS_ID_REG, - CFAPI_SHELLEXT_THUMBNAIL_HANDLER_CLASS_ID_REG - }; + const QStringList listExtensions = {CFAPI_SHELLEXT_CUSTOM_STATE_HANDLER_CLASS_ID_REG, CFAPI_SHELLEXT_THUMBNAIL_HANDLER_CLASS_ID_REG}; for (const auto &extension : listExtensions) { const QString clsidPath = QString() % clsIdRegKey % extension; @@ -97,7 +95,8 @@ void unregisterShellExtensions() } -namespace OCC { +namespace OCC +{ class VfsCfApiPrivate { @@ -129,7 +128,13 @@ void VfsCfApi::startImpl(const VfsSetupParams ¶ms) cfapi::registerShellExtension(); const auto localPath = QDir::toNativeSeparators(params.filesystemPath); - const auto registerResult = cfapi::registerSyncRoot(localPath, params.providerName, params.providerVersion, params.alias, params.navigationPaneClsid, params.displayName, params.account->displayName()); + const auto registerResult = cfapi::registerSyncRoot(localPath, + params.providerName, + params.providerVersion, + params.alias, + params.navigationPaneClsid, + params.displayName, + params.account->displayName()); if (!registerResult) { qCCritical(lcCfApi) << "Initialization failed, couldn't register sync root:" << registerResult.error(); return; @@ -177,7 +182,8 @@ bool VfsCfApi::isHydrating() const return !d->hydrationJobs.isEmpty(); } -OCC::Result VfsCfApi::updateMetadata(const SyncFileItem &syncItem, const QString &filePath, const QString &replacesFile) +OCC::Result +VfsCfApi::updateMetadata(const SyncFileItem &syncItem, const QString &filePath, const QString &replacesFile) { const auto localPath = QDir::toNativeSeparators(filePath); const auto replacesPath = QDir::toNativeSeparators(replacesFile); @@ -219,7 +225,12 @@ Result VfsCfApi::createPlaceholders(const QList auto allPlaceholdersInfo = QList{}; for (const auto &oneItem : items) { auto fileInfo = QFileInfo(_setupParams.filesystemPath + oneItem->_file); - allPlaceholdersInfo.emplace_back(std::move(fileInfo), fileInfo.fileName(), QDir::toNativeSeparators(fileInfo.fileName()).toStdWString(), oneItem->_fileId, oneItem->_modtime, oneItem->_size); + allPlaceholdersInfo.emplace_back(std::move(fileInfo), + fileInfo.fileName(), + QDir::toNativeSeparators(fileInfo.fileName()).toStdWString(), + oneItem->_fileId, + oneItem->_modtime, + oneItem->_size); } const auto result = cfapi::createPlaceholdersInfo(localPath, {allPlaceholdersInfo}); @@ -243,7 +254,8 @@ Result VfsCfApi::dehydratePlaceholder(const SyncFileItem &item) } } -Result VfsCfApi::convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &replacesFile, UpdateMetadataTypes updateType) +Result +VfsCfApi::convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &replacesFile, UpdateMetadataTypes updateType) { const auto localPath = FileSystem::longWinPath(QDir::toNativeSeparators(filename)); const auto replacesPath = FileSystem::longWinPath(QDir::toNativeSeparators(replacesFile)); @@ -290,8 +302,25 @@ bool VfsCfApi::statTypeVirtualFile(csync_file_stat_t *stat, void *statData) // it is a file... let's prevent it if (isDirectory) { if (hasCloudTag) { + auto placeholderState = CfGetPlaceholderStateFromAttributeTag(ffd->dwFileAttributes, ffd->dwReserved0); + +#if defined NEXTCLOUD_DEV + qCDebug(lcCfApi()) << stat->path << (CF_PLACEHOLDER_STATE_PLACEHOLDER & placeholderState ? "CF_PLACEHOLDER_STATE_PLACEHOLDER" : "NONE") + << (CF_PLACEHOLDER_STATE_SYNC_ROOT & placeholderState ? "CF_PLACEHOLDER_STATE_SYNC_ROOT" : "NONE") + << (CF_PLACEHOLDER_STATE_ESSENTIAL_PROP_PRESENT & placeholderState ? "CF_PLACEHOLDER_STATE_ESSENTIAL_PROP_PRESENT" : "NONE") + << (CF_PLACEHOLDER_STATE_IN_SYNC & placeholderState ? "CF_PLACEHOLDER_STATE_IN_SYNC" : "NONE") + << (CF_PLACEHOLDER_STATE_PARTIAL & placeholderState ? "CF_PLACEHOLDER_STATE_PARTIAL" : "NONE") + << (CF_PLACEHOLDER_STATE_PARTIALLY_ON_DISK & placeholderState ? "CF_PLACEHOLDER_STATE_PARTIALLY_ON_DISK" : "NONE") + << (CF_PLACEHOLDER_STATE_INVALID & placeholderState ? "CF_PLACEHOLDER_STATE_INVALID" : "NONE"); +#endif + ffd->dwFileAttributes &= ~FILE_ATTRIBUTE_REPARSE_POINT; - stat->type = CSyncEnums::ItemTypeVirtualDirectory; + if (CF_PLACEHOLDER_STATE_PARTIALLY_ON_DISK & placeholderState) { + stat->type = CSyncEnums::ItemTypeVirtualDirectory; + } else { + stat->type = CSyncEnums::ItemTypeDirectory; + } + return true; } return false; @@ -349,8 +378,7 @@ Vfs::AvailabilityResult VfsCfApi::availability(const QString &folderPath, const { const auto basePinState = pinState(folderPath); if (basePinState && recursiveCheck == Vfs::AvailabilityRecursivity::NotRecursiveAvailability) { - switch (*basePinState) - { + switch (*basePinState) { case OCC::PinState::AlwaysLocal: return VfsItemAvailability::AlwaysLocal; break; @@ -372,17 +400,20 @@ Vfs::AvailabilityResult VfsCfApi::availability(const QString &folderPath, const const auto hydrationStatus = hydrationAndPinStates.hydrationStatus; if (hydrationStatus.hasDehydrated) { - if (hydrationStatus.hasHydrated) + if (hydrationStatus.hasHydrated) { return VfsItemAvailability::Mixed; - if (pin && *pin == PinState::OnlineOnly) + } + if (pin && *pin == PinState::OnlineOnly) { return VfsItemAvailability::OnlineOnly; - else + } else { return VfsItemAvailability::AllDehydrated; + } } else if (hydrationStatus.hasHydrated) { - if (pin && *pin == PinState::AlwaysLocal) + if (pin && *pin == PinState::AlwaysLocal) { return VfsItemAvailability::AlwaysLocal; - else + } else { return VfsItemAvailability::AllHydrated; + } } else { if (pin && *pin == PinState::OnlineOnly) { return VfsItemAvailability::OnlineOnly; @@ -530,14 +561,12 @@ int VfsCfApi::finalizeHydrationJob(const QString &requestId) return HydrationJob::Status::Error; } -int VfsCfApi::finalizeNewPlaceholders(const QList &newEntries, - const QString &pathString) +int VfsCfApi::finalizeNewPlaceholders(const QList &newEntries, const QString &pathString) { const auto &journal = params().journal; for (const auto &entryInfo : newEntries) { - if (entryInfo.creationStatus == VirtualItemCreationStatus::Error || - entryInfo.creationStatus == VirtualItemCreationStatus::Unknown) { + if (entryInfo.creationStatus == VirtualItemCreationStatus::Error || entryInfo.creationStatus == VirtualItemCreationStatus::Unknown) { qCWarning(lcCfApi()) << "do not create DB record for virtual item not created" << entryInfo.fullPath; continue; } @@ -557,7 +586,8 @@ int VfsCfApi::finalizeNewPlaceholders(const QList &newEnt (entryInfo.parsedProperties.isDirectory ? ItemTypeVirtualDirectory : (FileSystem::isExcludeFile(entryInfo.fullPath) ? ItemTypeVirtualFileDownload : ItemTypeVirtualFile)); folderRecord._etag = entryInfo.parsedProperties.etag; - folderRecord._e2eEncryptionStatus = static_cast(entryInfo.parsedProperties.isE2eEncrypted() ? SyncFileItem::EncryptionStatus::EncryptedMigratedV2_0 : SyncFileItem::EncryptionStatus::NotEncrypted); + folderRecord._e2eEncryptionStatus = static_cast( + entryInfo.parsedProperties.isE2eEncrypted() ? SyncFileItem::EncryptionStatus::EncryptedMigratedV2_0 : SyncFileItem::EncryptionStatus::NotEncrypted); folderRecord._lockstate._locked = (entryInfo.parsedProperties.locked == SyncFileItemEnums::LockStatus::LockedItem); folderRecord._lockstate._lockOwnerDisplayName = entryInfo.parsedProperties.lockOwnerDisplayName; folderRecord._lockstate._lockOwnerId = entryInfo.parsedProperties.lockOwnerId; @@ -617,43 +647,36 @@ VfsCfApi::HydratationAndPinStates VfsCfApi::computeRecursiveHydrationAndPinState } const auto effectivePin = pinState(folderPath); const auto pinResult = (!effectivePin && !basePinState) ? Optional() - : (!effectivePin || !basePinState) ? PinState::Inherited - : (*effectivePin == *basePinState) ? *effectivePin - : PinState::Inherited; + : (!effectivePin || !basePinState) ? PinState::Inherited + : (*effectivePin == *basePinState) ? *effectivePin + : PinState::Inherited; if (FileSystem::isDir(fullPath)) { - const auto dirState = HydratationAndPinStates { - pinResult, - {} - }; + const auto dirState = HydratationAndPinStates{pinResult, {}}; const auto dir = QDir(info.absoluteFilePath()); Q_ASSERT(dir.exists()); const auto children = dir.entryList(); - return std::accumulate(std::cbegin(children), std::cend(children), dirState, [=, this](const HydratationAndPinStates ¤tState, const QString &name) { - if (name == QStringLiteral("..") || name == QStringLiteral(".")) { - return currentState; - } - - // if the folderPath.isEmpty() we don't want to end up having path "/example.file" because this will lead to double slash later, when appending to "SyncFolder/" - const auto path = folderPath.isEmpty() ? name : folderPath + '/' + name; - const auto states = computeRecursiveHydrationAndPinStates(path, currentState.pinState); - return HydratationAndPinStates { - states.pinState, - { - states.hydrationStatus.hasHydrated || currentState.hydrationStatus.hasHydrated, - states.hydrationStatus.hasDehydrated || currentState.hydrationStatus.hasDehydrated, - } - }; - }); + return std::accumulate(std::cbegin(children), + std::cend(children), + dirState, + [=, this](const HydratationAndPinStates ¤tState, const QString &name) { + if (name == QStringLiteral("..") || name == QStringLiteral(".")) { + return currentState; + } + + // if the folderPath.isEmpty() we don't want to end up having path "/example.file" because this will lead to double slash + // later, when appending to "SyncFolder/" + const auto path = folderPath.isEmpty() ? name : folderPath + '/' + name; + const auto states = computeRecursiveHydrationAndPinStates(path, currentState.pinState); + return HydratationAndPinStates{states.pinState, + { + states.hydrationStatus.hasHydrated || currentState.hydrationStatus.hasHydrated, + states.hydrationStatus.hasDehydrated || currentState.hydrationStatus.hasDehydrated, + }}; + }); } else { // file case const auto isDehydrated = isDehydratedPlaceholder(info.absoluteFilePath()); - return { - pinResult, - { - !isDehydrated, - isDehydrated - } - }; + return {pinResult, {!isDehydrated, isDehydrated}}; } } diff --git a/src/libsync/vfs/cfapi/vfs_cfapi.h b/src/libsync/vfs/cfapi/vfs_cfapi.h index 059b19aa51f52..3397bc5d2bc36 100644 --- a/src/libsync/vfs/cfapi/vfs_cfapi.h +++ b/src/libsync/vfs/cfapi/vfs_cfapi.h @@ -10,6 +10,8 @@ #include "common/vfs.h" #include "common/plugin.h" +#include + namespace OCC { class HydrationJob; class VfsCfApiPrivate; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8ba7f97d7945e..68bcd13d93741 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -185,6 +185,7 @@ if (WIN32) ) nextcloud_add_test(SyncCfApi) + target_link_libraries(SyncCfApiTest PRIVATE cldapi) nextcloud_add_test(CfApiShellExtensionsIPC) target_sources(CfApiShellExtensionsIPCTest PRIVATE "${CMAKE_SOURCE_DIR}/src/libsync/vfs/cfapi/shellext/thumbnailprovideripc.cpp" "${CMAKE_SOURCE_DIR}/src/libsync/vfs/cfapi/shellext/customstateprovideripc.cpp" "${CMAKE_SOURCE_DIR}/src/libsync/vfs/cfapi/shellext/ipccommon.cpp") elseif(LINUX) # elseif(LINUX OR APPLE) diff --git a/test/testsynccfapi.cpp b/test/testsynccfapi.cpp index 8c64331fcd977..874ca12f8dbea 100644 --- a/test/testsynccfapi.cpp +++ b/test/testsynccfapi.cpp @@ -76,6 +76,17 @@ SyncJournalFileRecord dbRecord(FakeFolder &folder, const QString &path) return record; } +CF_PLACEHOLDER_STATE placeholderState(const QString &path) +{ + WIN32_FIND_DATAW findData = {}; + const auto handle = FindFirstFileW(reinterpret_cast(QDir::toNativeSeparators(path).utf16()), &findData); + if (handle == INVALID_HANDLE_VALUE) { + return CF_PLACEHOLDER_STATE_INVALID; + } + FindClose(handle); + return CfGetPlaceholderStateFromAttributeTag(findData.dwFileAttributes, findData.dwReserved0); +} + void triggerDownload(FakeFolder &folder, const QByteArray &path) { auto &journal = folder.syncJournal(); @@ -1432,6 +1443,68 @@ private Q_SLOTS: QVERIFY(itemInstruction(completeSpy, "A", CSYNC_INSTRUCTION_NONE)); } + void testFolderPopulationFlags_data() + { + QTest::addColumn("isRemoteFolder"); + QTest::addColumn("visitFolder"); + QTest::addColumn("expectFullySynced"); + + QTest::newRow("local new folder") << false << false << true; + QTest::newRow("locally visited folder") << true << true << true; + QTest::newRow("server new folder") << true << false << false; + } + + void testFolderPopulationFlags() + { + QFETCH(bool, isRemoteFolder); + QFETCH(bool, visitFolder); + QFETCH(bool, expectFullySynced); + + FakeFolder fakeFolder{FileInfo{}}; + setupVfs(fakeFolder); + + if (isRemoteFolder) { + fakeFolder.remoteModifier().mkdir("A"); + fakeFolder.remoteModifier().insert("A/a1"); + } else { + fakeFolder.localModifier().mkdir("A"); + } + QVERIFY(fakeFolder.syncOnce()); + + if (visitFolder) { + OCC::showInFileManager(fakeFolder.localPath() + "A"); + QTest::qWait(5000); + QVERIFY(fakeFolder.syncOnce()); + + OCC::showInFileManager(fakeFolder.localPath() + "A"); + QTest::qWait(5000); + QVERIFY(fakeFolder.syncOnce()); + } + + if (isRemoteFolder) { + QCOMPARE(QFileInfo::exists(fakeFolder.localPath() + "A/a1"), expectFullySynced); + } + + const auto expectedType = expectFullySynced ? ItemTypeDirectory : ItemTypeVirtualDirectory; + const auto verifyFolderState = [&] { + const auto state = placeholderState(fakeFolder.localPath() + "A"); + QVERIFY(state & CF_PLACEHOLDER_STATE_PLACEHOLDER); + QVERIFY(state & CF_PLACEHOLDER_STATE_IN_SYNC); + QCOMPARE(static_cast(state & CF_PLACEHOLDER_STATE_PARTIALLY_ON_DISK), !expectFullySynced); + QCOMPARE(dbRecord(fakeFolder, "A")._type, expectedType); + }; + + verifyFolderState(); + + // local discovery must find the same folder type again and leave the folder untouched + fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly); + ItemCompletedSpy completeSpy(fakeFolder); + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(completeSpy.findItem("A")->_instruction, CSYNC_INSTRUCTION_NONE); + + verifyFolderState(); + } + void testRemoteTypeChangeExistingLocalMustGetRemoved() { FakeFolder fakeFolder{FileInfo{}};