mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-22 21:15:55 +03:00
Merge pull request #2981 from nextcloud/vfs_win_progress_bar_refresh
Refresh Windows download dialog progress when hydrating a placeholder
This commit is contained in:
commit
e3a26477a2
10 changed files with 204 additions and 15 deletions
|
@ -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 /.
|
||||
|
|
|
@ -380,6 +380,7 @@ void AccountManager::deleteAccount(AccountState *account)
|
|||
// Forget E2E keys
|
||||
account->account()->e2e()->forgetSensitiveData(account->account());
|
||||
|
||||
emit accountSyncConnectionRemoved(account);
|
||||
emit accountRemoved(account);
|
||||
}
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@ public slots:
|
|||
Q_SIGNALS:
|
||||
void accountAdded(AccountState *account);
|
||||
void accountRemoved(AccountState *account);
|
||||
void accountSyncConnectionRemoved(AccountState *account);
|
||||
void removeAccountFolders(AccountState *account);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
@ -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;
|
||||
|
|
|
@ -206,6 +206,8 @@ public:
|
|||
*/
|
||||
virtual void wipeForRemoval();
|
||||
|
||||
void onAssociatedAccountRemoved();
|
||||
|
||||
void setSyncState(SyncResult::Status state);
|
||||
|
||||
void setDirtyNetworkLimits();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <QLocalSocket>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include <sddl.h>
|
||||
#include <cfapi.h>
|
||||
#include <comdef.h>
|
||||
#include <ntstatus.h>
|
||||
|
@ -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 };
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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 ®istryKeyToSet : 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();
|
||||
|
@ -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);
|
||||
|
@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,8 +71,8 @@ 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<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);
|
||||
|
|
|
@ -63,7 +63,7 @@ void VfsCfApi::startImpl(const VfsSetupParams ¶ms)
|
|||
{
|
||||
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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue