Skip to content

Commit

Permalink
Refresh Windows download dialog progress when hydrating a placeholder
Browse files Browse the repository at this point in the history
Signed-off-by: allexzander <blackslayer4@gmail.com>
  • Loading branch information
allexzander committed Mar 24, 2021
1 parent 6a53491 commit 193e503
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 15 deletions.
6 changes: 6 additions & 0 deletions src/common/vfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ struct OCSYNC_EXPORT VfsSetupParams
*/
QString filesystemPath;

// Folder display name in Windows Explorer
QString displayName;

// Folder alias
QString alias;

/** The path to the synced folder on the account
*
* Always ends with /.
Expand Down
1 change: 1 addition & 0 deletions src/gui/accountmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ void AccountManager::deleteAccount(AccountState *account)
// Forget E2E keys
account->account()->e2e()->forgetSensitiveData(account->account());

emit accountSyncConnectionRemoved(account);
emit accountRemoved(account);
}

Expand Down
1 change: 1 addition & 0 deletions src/gui/accountmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public slots:
Q_SIGNALS:
void accountAdded(AccountState *account);
void accountRemoved(AccountState *account);
void accountSyncConnectionRemoved(AccountState *account);
void removeAccountFolders(AccountState *account);
};
}
10 changes: 10 additions & 0 deletions src/gui/folder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,14 @@ void Folder::setSyncPaused(bool paused)
emit canSyncChanged();
}

void Folder::onAssociatedAccountRemoved()
{
if (_vfs) {
_vfs->stop();
_vfs->unregisterFolder();
}
}

void Folder::setSyncState(SyncResult::Status state)
{
_syncResult.setStatus(state);
Expand Down Expand Up @@ -486,6 +494,8 @@ void Folder::startVfs()

VfsSetupParams vfsParams;
vfsParams.filesystemPath = path();
vfsParams.displayName = shortGuiRemotePathOrAppName();
vfsParams.alias = alias();
vfsParams.remotePath = remotePathTrailingSlash();
vfsParams.account = _accountState->account();
vfsParams.journal = &_journal;
Expand Down
2 changes: 2 additions & 0 deletions src/gui/folder.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ class Folder : public QObject
*/
virtual void wipeForRemoval();

void onAssociatedAccountRemoved();

void setSyncState(SyncResult::Status state);

void setDirtyNetworkLimits();
Expand Down
12 changes: 12 additions & 0 deletions src/gui/folderman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ FolderMan::FolderMan(QObject *parent)
connect(AccountManager::instance(), &AccountManager::removeAccountFolders,
this, &FolderMan::slotRemoveFoldersForAccount);

connect(AccountManager::instance(), &AccountManager::accountSyncConnectionRemoved,
this, &FolderMan::slotAccountRemoved);

connect(_lockWatcher.data(), &LockWatcher::fileUnlocked,
this, &FolderMan::slotWatchedFileUnlocked);

Expand Down Expand Up @@ -902,6 +905,15 @@ void FolderMan::runEtagJobIfPossible(Folder *folder)
QMetaObject::invokeMethod(folder, "slotRunEtagJob", Qt::QueuedConnection);
}

void FolderMan::slotAccountRemoved(AccountState *accountState)
{
for (const auto &folder : qAsConst(_folderMap)) {
if (folder->accountState() == accountState) {
folder->onAssociatedAccountRemoved();
}
}
}

void FolderMan::slotRemoveFoldersForAccount(AccountState *accountState)
{
QVarLengthArray<Folder *, 16> foldersToRemove;
Expand Down
2 changes: 2 additions & 0 deletions src/gui/folderman.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ private slots:
void slotStartScheduledFolderSync();
void slotEtagPollTimerTimeout();

void slotAccountRemoved(AccountState *accountState);

void slotRemoveFoldersForAccount(AccountState *accountState);

// Wraps the Folder::syncStateChange() signal into the
Expand Down
176 changes: 166 additions & 10 deletions src/libsync/vfs/cfapi/cfapiwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <QLocalSocket>
#include <QLoggingCategory>

#include <sddl.h>
#include <cfapi.h>
#include <comdef.h>
#include <ntstatus.h>
Expand All @@ -36,7 +37,7 @@ Q_LOGGING_CATEGORY(lcCfApiWrapper, "nextcloud.sync.vfs.cfapi.wrapper", QtInfoMsg
FIELD_SIZE( CF_OPERATION_PARAMETERS, field ) )

namespace {
void cfApiSendTransferInfo(const CF_CONNECTION_KEY &connectionKey, const CF_TRANSFER_KEY &transferKey, NTSTATUS status, void *buffer, qint64 offset, qint64 length)
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 };
Expand All @@ -50,11 +51,24 @@ void cfApiSendTransferInfo(const CF_CONNECTION_KEY &connectionKey, const CF_TRAN
opParams.TransferData.CompletionStatus = status;
opParams.TransferData.Buffer = buffer;
opParams.TransferData.Offset.QuadPart = offset;
opParams.TransferData.Length.QuadPart = length;
opParams.TransferData.Length.QuadPart = currentBlockLength;

const qint64 result = CfExecute(&opInfo, &opParams);
if (result != S_OK) {
qCCritical(lcCfApiWrapper) << "Couldn't send transfer info" << QString::number(transferKey.QuadPart, 16) << ":" << result << QString::fromWCharArray(_com_error(result).ErrorMessage());
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());
}

// refresh Windows Copy Dialog progress
LARGE_INTEGER progressTotal;
progressTotal.QuadPart = totalLength;

LARGE_INTEGER progressCompleted;
progressCompleted.QuadPart = offset;

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());
}
}

Expand All @@ -67,7 +81,8 @@ void CALLBACK cfApiFetchDataCallback(const CF_CALLBACK_INFO *callbackInfo, const
STATUS_UNSUCCESSFUL,
nullptr,
callbackParameters->FetchData.RequiredFileOffset.QuadPart,
callbackParameters->FetchData.RequiredLength.QuadPart);
callbackParameters->FetchData.RequiredLength.QuadPart,
callbackInfo->FileSize.QuadPart);
};

const auto sendTransferInfo = [=](QByteArray &data, qint64 offset) {
Expand All @@ -77,7 +92,8 @@ void CALLBACK cfApiFetchDataCallback(const CF_CALLBACK_INFO *callbackInfo, const
STATUS_SUCCESS,
data.data(),
offset,
data.length());
data.length(),
callbackInfo->FileSize.QuadPart);
};

auto vfs = reinterpret_cast<OCC::VfsCfApi *>(callbackInfo->CallbackContext);
Expand Down Expand Up @@ -309,8 +325,141 @@ OCC::Optional<OCC::PinStateEnums::PinState> OCC::CfApiWrapper::PlaceHolderInfo::
return cfPinStateToPinState(_data->PinState);
}

OCC::Result<void, QString> OCC::CfApiWrapper::registerSyncRoot(const QString &path, const QString &providerName, const QString &providerVersion)
QString convertSidToStringSid(void *sid)
{
wchar_t *stringSid = nullptr;
if (!ConvertSidToStringSid(sid, &stringSid)) {
return {};
}

const auto result = QString::fromWCharArray(stringSid);
LocalFree(stringSid);
return result;
}

std::unique_ptr<TOKEN_USER> getCurrentTokenInformation()
{
const auto tokenHandle = GetCurrentThreadEffectiveToken();

auto tokenInfoSize = DWORD{0};

const auto tokenSizeCallSucceeded = ::GetTokenInformation(tokenHandle, TokenUser, nullptr, 0, &tokenInfoSize);
const auto lastError = GetLastError();
Q_ASSERT(!tokenSizeCallSucceeded && lastError == ERROR_INSUFFICIENT_BUFFER);
if (tokenSizeCallSucceeded || lastError != ERROR_INSUFFICIENT_BUFFER) {
qCCritical(lcCfApiWrapper) << "GetTokenInformation for token size has failed with error" << lastError;
return {};
}

std::unique_ptr<TOKEN_USER> tokenInfo;

tokenInfo.reset(reinterpret_cast<TOKEN_USER*>(new char[tokenInfoSize]));
if (!::GetTokenInformation(tokenHandle, TokenUser, tokenInfo.get(), tokenInfoSize, &tokenInfoSize)) {
qCCritical(lcCfApiWrapper) << "GetTokenInformation failed with error" << lastError;
return {};
}

return tokenInfo;
}

QString retrieveWindowsSid()
{
if (const auto tokenInfo = getCurrentTokenInformation()) {
return convertSidToStringSid(tokenInfo->User.Sid);
}

return {};
}

bool createSyncRootRegistryKeys(const QString &providerName, const QString &folderAlias, 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
const auto windowsSid = retrieveWindowsSid();
Q_ASSERT(!windowsSid.isEmpty());
if (windowsSid.isEmpty()) {
qCWarning(lcCfApiWrapper) << "Failed to set Registry keys for shell integration, as windowsSid is empty. Progress bar will not work.";
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
const auto syncRootId = QString("%1!%2!%3!%4").arg(providerName).arg(windowsSid).arg(accountDisplayName).arg(folderAlias);

const QString providerSyncRootIdRegistryKey = QStringLiteral(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SyncRootManager\)") + syncRootId;
const QString providerSyncRootIdUserSyncRootsRegistryKey = providerSyncRootIdRegistryKey + QStringLiteral(R"(\UserSyncRoots\)");

struct RegistryKeyInfo {
QString subKey;
QString valueName;
int type;
QVariant value;
};

const QVector<RegistryKeyInfo> registryKeysToSet = {
{ providerSyncRootIdRegistryKey, QStringLiteral("Flags"), REG_DWORD, 34 },
{ 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 }
};

for (const auto &registryKeyToSet : qAsConst(registryKeysToSet)) {
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;
}
}

qCInfo(lcCfApiWrapper) << "Successfully set Registry keys for shell integration at:" << providerSyncRootIdRegistryKey << ". Progress bar will work.";

return true;
}

bool deleteSyncRootRegistryKey(const QString &syncRootPath, const QString &providerName, const QString &accountDisplayName)
{
const auto syncRootManagerRegistryKey = QStringLiteral(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SyncRootManager\)");

if (OCC::Utility::registryKeyExists(HKEY_LOCAL_MACHINE, syncRootManagerRegistryKey)) {
const auto windowsSid = retrieveWindowsSid();
Q_ASSERT(!windowsSid.isEmpty());
if (windowsSid.isEmpty()) {
qCWarning(lcCfApiWrapper) << "Failed to delete Registry key for shell integration on path" << syncRootPath << ". Because windowsSid is empty.";
return false;
}

const auto currentUserSyncRootIdPattern = QString("%1!%2!%3").arg(providerName).arg(windowsSid).arg(accountDisplayName);

bool result = true;

// walk through each registered syncRootId
OCC::Utility::registryWalkSubKeys(HKEY_LOCAL_MACHINE, syncRootManagerRegistryKey, [&](HKEY, const QString &syncRootId) {
// make sure we have matching syncRootId(providerName!windowsSid!accountDisplayName)
if (syncRootId.startsWith(currentUserSyncRootIdPattern)) {
const QString syncRootIdUserSyncRootsRegistryKey = syncRootManagerRegistryKey + 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 = syncRootManagerRegistryKey + syncRootId;
result = OCC::Utility::registryDeleteKeyTree(HKEY_LOCAL_MACHINE, syncRootIdToDelete);
}
}
});
return result;
}
return true;
}

OCC::Result<void, QString> OCC::CfApiWrapper::registerSyncRoot(const QString &path, const QString &providerName, const QString &providerVersion, const QString &folderAlias, 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, displayName, accountDisplayName, path);
Q_ASSERT(createRegistryKeyResult);

if (!createRegistryKeyResult) {
qCWarning(lcCfApiWrapper) << "Failed to create the registry key for path:" << path;
}

const auto p = path.toStdWString();
const auto name = providerName.toStdWString();
const auto version = providerVersion.toStdWString();
Expand Down Expand Up @@ -340,8 +489,15 @@ OCC::Result<void, QString> OCC::CfApiWrapper::registerSyncRoot(const QString &pa
}
}

OCC::Result<void, QString> OCC::CfApiWrapper::unegisterSyncRoot(const QString &path)
OCC::Result<void, QString> OCC::CfApiWrapper::unregisterSyncRoot(const QString &path, const QString &providerName, const QString &accountDisplayName)
{
const auto deleteRegistryKeyResult = deleteSyncRootRegistryKey(path, providerName, accountDisplayName);
Q_ASSERT(deleteRegistryKeyResult);

if (!deleteRegistryKeyResult) {
qCWarning(lcCfApiWrapper) << "Failed to delete the registry key for path:" << path;
}

const auto p = path.toStdWString();
const qint64 result = CfUnregisterSyncRoot(p.data());
Q_ASSERT(result == S_OK);
Expand All @@ -365,7 +521,7 @@ OCC::Result<OCC::CfApiWrapper::ConnectionKey, QString> OCC::CfApiWrapper::connec
if (result != S_OK) {
return QString::fromWCharArray(_com_error(result).ErrorMessage());
} else {
return key;
return { std::move(key) };
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/libsync/vfs/cfapi/cfapiwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ class OWNCLOUDSYNC_EXPORT PlaceHolderInfo
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<void, QString> registerSyncRoot(const QString &path, const QString &providerName, const QString &providerVersion, const QString &folderAlias, const QString &displayName, const QString &accountDisplayName);
OWNCLOUDSYNC_EXPORT Result<void, QString> unregisterSyncRoot(const QString &path, const QString &providerName, const QString &accountDisplayName);

OWNCLOUDSYNC_EXPORT Result<ConnectionKey, QString> connectSyncRoot(const QString &path, VfsCfApi *context);
OWNCLOUDSYNC_EXPORT Result<void, QString> disconnectSyncRoot(ConnectionKey &&key);
Expand Down
5 changes: 2 additions & 3 deletions src/libsync/vfs/cfapi/vfs_cfapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ void VfsCfApi::startImpl(const VfsSetupParams &params)
{
const auto localPath = QDir::toNativeSeparators(params.filesystemPath);

const auto registerResult = cfapi::registerSyncRoot(localPath, params.providerName, params.providerVersion);
const auto registerResult = cfapi::registerSyncRoot(localPath, params.providerName, params.providerVersion, params.alias, params.displayName, params.account->displayName());
if (!registerResult) {
qCCritical(lcCfApi) << "Initialization failed, couldn't register sync root:" << registerResult.error();
return;
Expand All @@ -89,7 +89,7 @@ void VfsCfApi::stop()
void VfsCfApi::unregisterFolder()
{
const auto localPath = QDir::toNativeSeparators(params().filesystemPath);
const auto result = cfapi::unegisterSyncRoot(localPath);
const auto result = cfapi::unregisterSyncRoot(localPath, params().providerName, params().account->displayName());
if (!result) {
qCCritical(lcCfApi) << "Unregistration failed for" << localPath << ":" << result.error();
}
Expand Down Expand Up @@ -388,7 +388,6 @@ VfsCfApi::HydratationAndPinStates VfsCfApi::computeRecursiveHydrationAndPinState
if (!info.exists()) {
return {};
}

const auto effectivePin = pinState(folderPath);
const auto pinResult = (!effectivePin && !basePinState) ? Optional<PinState>()
: (!effectivePin || !basePinState) ? PinState::Inherited
Expand Down

0 comments on commit 193e503

Please sign in to comment.