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 11, 2021
1 parent 0ce965e commit 341482d
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 7 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
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::accountRemoved,
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
127 changes: 124 additions & 3 deletions src/libsync/vfs/cfapi/cfapiwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <QLocalSocket>
#include <QLoggingCategory>

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

namespace {
QMap<QString, QString> registeredSyncRootKeys;
void cfApiSendTransferInfo(const CF_CONNECTION_KEY &connectionKey, const CF_TRANSFER_KEY &transferKey, NTSTATUS status, void *buffer, qint64 offset, qint64 length)
{

Expand Down Expand Up @@ -78,6 +80,11 @@ void CALLBACK cfApiFetchDataCallback(const CF_CALLBACK_INFO *callbackInfo, const
data.data(),
offset,
data.length());

LARGE_INTEGER progressCompleted;
progressCompleted.QuadPart = offset;

CfReportProviderProgress(callbackInfo->ConnectionKey, callbackInfo->TransferKey, callbackInfo->FileSize, progressCompleted);
};

auto vfs = reinterpret_cast<OCC::VfsCfApi *>(callbackInfo->CallbackContext);
Expand Down Expand Up @@ -290,8 +297,120 @@ 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(_In_ PSID sid)
{
QString result;
wchar_t *stringSid = nullptr;
if (::ConvertSidToStringSid(sid, &stringSid))
{
result = QString::fromWCharArray(stringSid);
}

if (stringSid) {
::LocalFree(stringSid);
stringSid = nullptr;
}

return result;
}

QString retrieveWindowsSID()
{
// FYI: code here is mostly identical to Windows-Classic-Examples/Samples/CloudMirror
QScopedPointer<TOKEN_USER> tokenInfo;

const auto tokenHandle = GetCurrentThreadEffectiveToken();

DWORD tokenInfoSize = { 0 };
// first - get the required size
if (!::GetTokenInformation(tokenHandle, TokenUser, nullptr, 0, &tokenInfoSize))
{
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// now, use the obtained size to allocate TOKEN_USER
tokenInfo.reset(reinterpret_cast<TOKEN_USER*>(new char[tokenInfoSize]));
if (!::GetTokenInformation(tokenHandle, TokenUser, tokenInfo.get(), tokenInfoSize, &tokenInfoSize)) {
return QString();
}
}
}

if (!tokenInfo) {
return QString();
}

return convertSidToStringSid(tokenInfo->User.Sid);
}

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 = QString() % R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SyncRootManager\)" % syncRootId;
const QString providerSyncRootIdUserSyncRootsRegistryKey = QString() % providerSyncRootIdRegistryKey % R"(\UserSyncRoots\)";

const QVector<std::tuple<HKEY, QString, QString, int, QVariant>> registryKeysToSet = {
{ HKEY_LOCAL_MACHINE, providerSyncRootIdRegistryKey, QStringLiteral("Flags"), REG_DWORD, 34 },
{ HKEY_LOCAL_MACHINE, providerSyncRootIdRegistryKey, QStringLiteral("DisplayNameResource"), REG_EXPAND_SZ, displayName },
{ HKEY_LOCAL_MACHINE, providerSyncRootIdRegistryKey, QStringLiteral("IconResource"), REG_EXPAND_SZ, QString(QDir::toNativeSeparators(qApp->applicationFilePath()) + QString(",0")) },
{ HKEY_LOCAL_MACHINE, providerSyncRootIdUserSyncRootsRegistryKey, windowsSID, REG_SZ, syncRootPath }
};

bool isAnyKeySetFailed = false;

for (const auto &registryKeyToSet : qAsConst(registryKeysToSet)) {
HKEY rootKey;
QString subKey;
QString valueName;
int type;
QVariant value;

std::tie(rootKey, subKey, valueName, type, value) = registryKeyToSet;

if ((isAnyKeySetFailed = !OCC::Utility::registrySetKeyValue(rootKey, subKey, valueName, type, value))) {
break;
}
}

if (isAnyKeySetFailed) {
qCWarning(lcCfApiWrapper) << "Failed to set Registry keys for shell integration. Progress bar will not work.";
Q_ASSERT(!OCC::Utility::registryDeleteKeyTree(HKEY_LOCAL_MACHINE, providerSyncRootIdRegistryKey));
} else {
registeredSyncRootKeys[syncRootPath] = providerSyncRootIdRegistryKey;
qCInfo(lcCfApiWrapper) << "Successfully set Registry keys for shell integration at:" << providerSyncRootIdRegistryKey << ". Progress bar will work.";
}

return !isAnyKeySetFailed;
}

bool deleteSyncRootRegistryKey(const QString& syncRootPath)
{
const auto foundRegisteredSyncRootKey = registeredSyncRootKeys.find(syncRootPath);
if (foundRegisteredSyncRootKey != registeredSyncRootKeys.end() && !foundRegisteredSyncRootKey->isEmpty()) {
bool result = OCC::Utility::registryDeleteKeyTree(HKEY_LOCAL_MACHINE, *foundRegisteredSyncRootKey);
registeredSyncRootKeys.remove(*foundRegisteredSyncRootKey);
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
Q_ASSERT(createSyncRootRegistryKeys(providerName, folderAlias, displayName, accountDisplayName, path));

const auto p = path.toStdWString();
const auto name = providerName.toStdWString();
const auto version = providerVersion.toStdWString();
Expand Down Expand Up @@ -321,8 +440,10 @@ 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)
{
Q_ASSERT(deleteSyncRootRegistryKey(path));

const auto p = path.toStdWString();
const qint64 result = CfUnregisterSyncRoot(p.data());
Q_ASSERT(result == S_OK);
Expand All @@ -346,7 +467,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);

OWNCLOUDSYNC_EXPORT Result<ConnectionKey, QString> connectSyncRoot(const QString &path, VfsCfApi *context);
OWNCLOUDSYNC_EXPORT Result<void, QString> disconnectSyncRoot(ConnectionKey &&key);
Expand Down
4 changes: 2 additions & 2 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);
if (!result) {
qCCritical(lcCfApi) << "Unregistration failed for" << localPath << ":" << result.error();
}
Expand Down

0 comments on commit 341482d

Please sign in to comment.