diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h index 0744006d21..4bb85c3a82 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h @@ -10,7 +10,11 @@ @protocol ClientCommunicationProtocol -- (void)getExtensionAccountIdWithCompletionHandler:(void(^)(NSString *extensionAccountId, NSError *error))completionHandler; +/** + * @brief Get the raw file provider domain identifier value. + */ +- (void)getFileProviderDomainIdentifierWithCompletionHandler:(void(^)(NSString *extensionAccountId, NSError *error))completionHandler; + - (void)configureAccountWithUser:(NSString *)user userId:(NSString *)userId serverUrl:(NSString *)serverUrl diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift index 7eb4689716..44f516d395 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift @@ -34,10 +34,10 @@ class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCLi //MARK: - Client Communication Protocol methods - func getExtensionAccountId(completionHandler: @escaping (String?, Error?) -> Void) { - let accountUserId = self.fpExtension.domain.identifier.rawValue - Logger.desktopClientConnection.info("Sending extension account ID \(accountUserId, privacy: .public)") - completionHandler(accountUserId, nil) + func getFileProviderDomainIdentifier(completionHandler: @escaping (String?, Error?) -> Void) { + let identifier = self.fpExtension.domain.identifier.rawValue + Logger.desktopClientConnection.info("Returning file provider domain identifier \(identifier, privacy: .public)") + completionHandler(identifier, nil) } func configureAccount( diff --git a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a950b309a4..cba14f4bdb 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -25,7 +25,7 @@ "location" : "https://github.com/nextcloud/NextcloudFileProviderKit.git", "state" : { "branch" : "main", - "revision" : "3e00b22a199ae01d8f70438a854af79e0046b672" + "revision" : "e2633ed6dddebd0abe5735d59dbcaaeaebc0acae" } }, { diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index 3aaba9b60a..76c94eb2a8 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -630,6 +630,7 @@ AccountStatePtr AccountManager::account(const QString &name) AccountStatePtr AccountManager::accountFromUserId(const QString &id) const { const auto accountsList = accounts(); + for (const auto &account : accountsList) { const auto isUserIdWithPort = id.split(QLatin1Char(':')).size() > 1; const auto port = isUserIdWithPort ? account->account()->url().port() : -1; @@ -640,6 +641,7 @@ AccountStatePtr AccountManager::accountFromUserId(const QString &id) const return account; } } + return {}; } diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp index 34cfd0a662..10271ecc1e 100644 --- a/src/gui/generalsettings.cpp +++ b/src/gui/generalsettings.cpp @@ -153,7 +153,7 @@ bool createDebugArchive(const QString &filename) const auto account = accountState->account(); const auto vfsLogFilename = QStringLiteral("macOS_vfs_%1.log").arg(account->davUser()); const auto vfsLogPath = tempDir.filePath(vfsLogFilename); - xpc->createDebugArchiveForExtension(accountUserIdAtHost, vfsLogPath); + xpc->createDebugArchiveForFileProviderDomain(accountUserIdAtHost, vfsLogPath); zip.addLocalFile(vfsLogPath, vfsLogFilename); } } diff --git a/src/gui/macOS/fileprovider_mac.mm b/src/gui/macOS/fileprovider_mac.mm index 836d24a10a..17cf2fe7ed 100644 --- a/src/gui/macOS/fileprovider_mac.mm +++ b/src/gui/macOS/fileprovider_mac.mm @@ -80,8 +80,8 @@ void FileProvider::configureXPC() _xpc = std::make_unique(new FileProviderXPC(this)); if (_xpc) { qCInfo(lcMacFileProvider) << "Initialised file provider XPC."; - _xpc->connectToExtensions(); - _xpc->configureExtensions(); + _xpc->connectToFileProviderDomains(); + _xpc->authenticateFileProviderDomains(); } else { qCWarning(lcMacFileProvider) << "Could not initialise file provider XPC."; } diff --git a/src/gui/macOS/fileproviderdomainmanager_mac.mm b/src/gui/macOS/fileproviderdomainmanager_mac.mm index fca15d6e82..1c6f6a17ff 100644 --- a/src/gui/macOS/fileproviderdomainmanager_mac.mm +++ b/src/gui/macOS/fileproviderdomainmanager_mac.mm @@ -9,11 +9,12 @@ #include #include -#include +#include #include "config.h" #include "fileproviderdomainmanager.h" #include "fileprovidersettingscontroller.h" +#include "fileproviderutils.h" #include "gui/accountmanager.h" #include "libsync/account.h" @@ -28,59 +29,44 @@ Q_LOGGING_CATEGORY(lcMacFileProviderDomainManager, "nextcloud.gui.macfileprovide // are consistent throughout these classes namespace { -static constexpr auto bundleExtensions = std::array{ - QLatin1StringView(".app"), - QLatin1StringView(".framework"), - QLatin1StringView(".kext"), - QLatin1StringView(".plugin"), - QLatin1StringView(".docset"), - QLatin1StringView(".xpc"), - QLatin1StringView(".qlgenerator"), - QLatin1StringView(".component"), - QLatin1StringView(".saver"), - QLatin1StringView(".mdimporter") -}; -static const QRegularExpression illegalChars("[:/]"); - -inline bool hasBundleExtension(const QString &domainId) -{ - return std::any_of(bundleExtensions.begin(), bundleExtensions.end(), [&domainId](const auto &ext) { - return domainId.endsWith(ext); - }); -} - -inline bool illegalDomainIdentifier(const QString &domainId) -{ - return !domainId.isEmpty() && !illegalChars.match(domainId).hasMatch() && hasBundleExtension(domainId); -} - -QString domainIdentifierForAccount(const OCC::Account * const account) +QString uuidDomainIdentifierForAccount(const OCC::Account * const account) { Q_ASSERT(account); - auto domainId = account->userIdAtHostWithPort(); - Q_ASSERT(!domainId.isEmpty()); - - domainId.replace(illegalChars, "-"); - - // Some url domains like .app cause issues on macOS as these are also bundle extensions. - // Under the hood, fileproviderd will create a folder for the user to access the files named - // after the domain identifier. If the url domain is the same as a bundle extension, Finder - // will interpret this folder as a bundle and will not allow the user to access the files. - // Here we wrap the dot in the url domain extension to prevent this from happening. - for (const auto &ext : bundleExtensions) { - if (domainId.endsWith(ext)) { - domainId = domainId.left(domainId.length() - ext.length()); - domainId += "(.)" + ext.right(ext.length() - 1); - break; - } + const auto accountId = account->userIdAtHostWithPort(); + + if (accountId.isEmpty()) { + qCWarning(OCC::lcMacFileProviderDomainManager) << "Cannot generate UUID for account with empty userIdAtHostWithPort"; + return {}; } + + // Try to get existing UUID mapping first + OCC::ConfigFile cfg; + const QString existingUuid = cfg.fileProviderDomainUuidFromAccountId(accountId); - return domainId; + if (!existingUuid.isEmpty()) { + qCDebug(OCC::lcMacFileProviderDomainManager) << "Using existing UUID for account:" + << accountId + << "UUID:" + << existingUuid; + + return existingUuid; + } + + // Generate new UUID for this account + const QString newUuid = QUuid::createUuid().toString(QUuid::WithoutBraces); + + qCInfo(OCC::lcMacFileProviderDomainManager) << "Generated new UUID for account:" + << accountId + << "UUID:" + << newUuid; + + cfg.setFileProviderDomainUuidForAccountId(accountId, newUuid); + return newUuid; } -inline QString domainIdentifierForAccount(const OCC::AccountPtr account) +inline QString uuidDomainIdentifierForAccount(const OCC::AccountPtr account) { - return domainIdentifierForAccount(account.get()); + return uuidDomainIdentifierForAccount(account.get()); } inline QString domainDisplayNameForAccount(const OCC::Account * const account) @@ -96,26 +82,90 @@ inline QString domainDisplayNameForAccount(const OCC::AccountPtr account) inline QString accountIdFromDomainId(const QString &domainId) { + if (domainId.isEmpty()) { + return {}; + } + + // Check if this is a UUID-based domain identifier + if (QUuid::fromString(domainId).isNull() == false) { + // This is a UUID, look up the account ID from the mapping + qCDebug(OCC::lcMacFileProviderDomainManager) << "Resolving UUID-based domain ID:" + << domainId; + + OCC::ConfigFile cfg; + const QString accountId = cfg.accountIdFromFileProviderDomainUuid(domainId); + + if (!accountId.isEmpty()) { + qCDebug(OCC::lcMacFileProviderDomainManager) << "UUID maps to account:" + << accountId; + + return accountId; + } + + qCWarning(OCC::lcMacFileProviderDomainManager) << "Could not find account id for UUID-based domain id:" + << domainId; + + return {}; + } + + // This is a legacy account-based domain identifier + qCDebug(OCC::lcMacFileProviderDomainManager) << "Using legacy account-based domain ID:" + << domainId; + return domainId; } QString accountIdFromDomainId(NSString * const domainId) { + if (!domainId) { + return {}; + } + auto qDomainId = QString::fromNSString(domainId); + + if (qDomainId.isEmpty()) { + return {}; + } + + // Check if this is a UUID-based domain identifier + if (QUuid::fromString(qDomainId).isNull() == false) { + // This is a UUID, look up the account ID from the mapping + qCDebug(OCC::lcMacFileProviderDomainManager) << "Resolving UUID-based domain ID from NSString:" << qDomainId; + OCC::ConfigFile cfg; + const QString accountId = cfg.accountIdFromFileProviderDomainUuid(qDomainId); + + if (!accountId.isEmpty()) { + qCDebug(OCC::lcMacFileProviderDomainManager) << "UUID maps to account:" << accountId; + return accountId; + } + + qCWarning(OCC::lcMacFileProviderDomainManager) << "Could not find account id for UUID-based domain id:" << qDomainId; + + return {}; + } + + // This is a legacy account-based domain identifier - handle the old logic + qCDebug(OCC::lcMacFileProviderDomainManager) << "Processing legacy account-based domain ID from NSString:" << qDomainId; + if (!qDomainId.contains('-')) { return qDomainId.replace("(.)", "."); } + // Using slashes as the replacement for illegal chars was unwise and we now have to pay the // price of doing so... const auto accounts = OCC::AccountManager::instance()->accounts(); + for (const auto &accountState : accounts) { const auto account = accountState->account(); - const auto convertedDomainId = domainIdentifierForAccount(account); + const auto convertedDomainId = OCC::Mac::FileProviderUtils::domainIdentifierForAccount(account); + if (convertedDomainId == qDomainId) { return account->userIdAtHostWithPort(); } } + qCWarning(OCC::lcMacFileProviderDomainManager) << "Could not find account id for domain id:" << qDomainId; + return {}; } @@ -154,7 +204,7 @@ public: } if (domains.count == 0) { - qCInfo(lcMacFileProviderDomainManager) << "Found no existing file provider domains"; + qCInfo(lcMacFileProviderDomainManager) << "Found no existing file provider domains at all."; dispatch_group_leave(dispatchGroup); return; } @@ -175,7 +225,7 @@ public: << accountState->account()->displayName(); [domain retain]; - if (illegalDomainIdentifier(QString::fromNSString(domain.identifier))) { + if (OCC::Mac::FileProviderUtils::illegalDomainIdentifier(QString::fromNSString(domain.identifier))) { qCWarning(lcMacFileProviderDomainManager) << "Found existing file provider domain with illegal domain identifier:" << domain.identifier << "and display name:" @@ -244,9 +294,9 @@ public: const auto account = accountState->account(); Q_ASSERT(account); - const auto domainId = domainIdentifierForAccount(account); - if (_registeredDomains.contains(domainId)) { - return _registeredDomains[domainId]; + const auto accountId = account->userIdAtHostWithPort(); + if (_registeredDomains.contains(accountId)) { + return _registeredDomains[accountId]; } } @@ -261,16 +311,18 @@ public: Q_ASSERT(account); const auto domainDisplayName = domainDisplayNameForAccount(account); - const auto domainId = domainIdentifierForAccount(account); + const auto domainId = uuidDomainIdentifierForAccount(account); // Use UUID for new domains + const auto accountId = account->userIdAtHostWithPort(); - qCInfo(lcMacFileProviderDomainManager) << "Adding new file provider domain with id:" + qCInfo(lcMacFileProviderDomainManager) << "Adding new file provider domain with UUID: " << domainId - << "and display name:" - << domainDisplayName; + << "for account:" + << accountId; - if (_registeredDomains.contains(domainId) && _registeredDomains.value(domainId) != nil) { - qCDebug(lcMacFileProviderDomainManager) << "File provider domain with id already exists: " - << domainId; + // Check if we already have a domain for this account (by account ID, not domain ID) + if (_registeredDomains.contains(accountId) && _registeredDomains.value(accountId) != nil) { + qCDebug(lcMacFileProviderDomainManager) << "File provider domain already exists for account: " + << accountId; return; } @@ -284,7 +336,7 @@ public: << error.localizedDescription; } - _registeredDomains.insert(domainId, fileProviderDomain); + _registeredDomains.insert(accountId, fileProviderDomain); // Store by account ID for easier lookup }]; } } @@ -296,17 +348,17 @@ public: const auto account = accountState->account(); Q_ASSERT(account); - const auto domainId = domainIdentifierForAccount(account); - qCInfo(lcMacFileProviderDomainManager) << "Removing file provider domain with id: " - << domainId; + const auto accountId = account->userIdAtHostWithPort(); + qCInfo(lcMacFileProviderDomainManager) << "Removing file provider domain for account: " + << accountId; - if (!_registeredDomains.contains(domainId)) { - qCWarning(lcMacFileProviderDomainManager) << "File provider domain not found for id: " - << domainId; + if (!_registeredDomains.contains(accountId)) { + qCWarning(lcMacFileProviderDomainManager) << "File provider domain not found for account: " + << accountId; return; } - NSFileProviderDomain * const fileProviderDomain = _registeredDomains[domainId]; + NSFileProviderDomain * const fileProviderDomain = _registeredDomains[accountId]; [NSFileProviderManager removeDomain:fileProviderDomain completionHandler:^(NSError *error) { if (error) { @@ -315,10 +367,12 @@ public: << error.localizedDescription; } - NSFileProviderDomain * const domain = _registeredDomains.take(domainId); + NSFileProviderDomain * const domain = _registeredDomains.take(accountId); [domain release]; - _registeredDomains.remove(domainId); + // Clean up the UUID mapping when removing the domain + OCC::ConfigFile cfg; + cfg.removeFileProviderDomainUuidMapping(accountId); }]; } } @@ -337,11 +391,17 @@ public: } const auto registeredDomainPtrs = _registeredDomains.values(); + const auto accountIds = _registeredDomains.keys(); for (NSFileProviderDomain * const domain : registeredDomainPtrs) { if (domain != nil) { [domain release]; } } + // Clean up UUID mappings for all accounts + OCC::ConfigFile cfg; + for (const QString &accountId : accountIds) { + cfg.removeFileProviderDomainUuidMapping(accountId); + } _registeredDomains.clear(); }]; } @@ -372,9 +432,15 @@ public: return; } - NSFileProviderDomain * const registeredDomainPtr = _registeredDomains.take(QString::fromNSString(domain.identifier)); + const QString accountId = accountIdFromDomainId(domain.identifier); + NSFileProviderDomain * const registeredDomainPtr = _registeredDomains.take(accountId); if (registeredDomainPtr != nil) { [domain release]; + // Clean up UUID mapping when wiping domain + if (!accountId.isEmpty()) { + OCC::ConfigFile cfg; + cfg.removeFileProviderDomainUuidMapping(accountId); + } } }]; } @@ -392,17 +458,17 @@ public: const auto account = accountState->account(); Q_ASSERT(account); - const auto domainId = domainIdentifierForAccount(account); - qCInfo(lcMacFileProviderDomainManager) << "Disconnecting file provider domain with id: " - << domainId; + const auto accountId = account->userIdAtHostWithPort(); + qCInfo(lcMacFileProviderDomainManager) << "Disconnecting file provider domain for account: " + << accountId; - if(!_registeredDomains.contains(domainId)) { - qCInfo(lcMacFileProviderDomainManager) << "File provider domain not found for id: " - << domainId; + if(!_registeredDomains.contains(accountId)) { + qCInfo(lcMacFileProviderDomainManager) << "File provider domain not found for account: " + << accountId; return; } - NSFileProviderDomain * const fileProviderDomain = _registeredDomains[domainId]; + NSFileProviderDomain * const fileProviderDomain = _registeredDomains[accountId]; Q_ASSERT(fileProviderDomain != nil); NSFileProviderManager * const fpManager = [NSFileProviderManager managerForDomain:fileProviderDomain]; @@ -430,17 +496,17 @@ public: const auto account = accountState->account(); Q_ASSERT(account); - const auto domainId = domainIdentifierForAccount(account); - qCInfo(lcMacFileProviderDomainManager) << "Reconnecting file provider domain with id: " - << domainId; + const auto accountId = account->userIdAtHostWithPort(); + qCInfo(lcMacFileProviderDomainManager) << "Reconnecting file provider domain for account: " + << accountId; - if(!_registeredDomains.contains(domainId)) { - qCInfo(lcMacFileProviderDomainManager) << "File provider domain not found for id: " - << domainId; + if(!_registeredDomains.contains(accountId)) { + qCInfo(lcMacFileProviderDomainManager) << "File provider domain not found for account: " + << accountId; return; } - NSFileProviderDomain * const fileProviderDomain = _registeredDomains[domainId]; + NSFileProviderDomain * const fileProviderDomain = _registeredDomains[accountId]; Q_ASSERT(fileProviderDomain != nil); NSFileProviderManager * const fpManager = [NSFileProviderManager managerForDomain:fileProviderDomain]; @@ -465,18 +531,18 @@ public: { if (@available(macOS 11.0, *)) { Q_ASSERT(account); - const auto domainId = domainIdentifierForAccount(account); + const auto accountId = account->userIdAtHostWithPort(); - qCInfo(lcMacFileProviderDomainManager) << "Signalling enumerator changed in file provider domain for account with id: " - << domainId; + qCInfo(lcMacFileProviderDomainManager) << "Signalling enumerator changed in file provider domain for account: " + << accountId; - if(!_registeredDomains.contains(domainId)) { - qCInfo(lcMacFileProviderDomainManager) << "File provider domain not found for id: " - << domainId; + if(!_registeredDomains.contains(accountId)) { + qCInfo(lcMacFileProviderDomainManager) << "File provider domain not found for account: " + << accountId; return; } - NSFileProviderDomain * const fileProviderDomain = _registeredDomains[domainId]; + NSFileProviderDomain * const fileProviderDomain = _registeredDomains[accountId]; Q_ASSERT(fileProviderDomain != nil); NSFileProviderManager * const fpManager = [NSFileProviderManager managerForDomain:fileProviderDomain]; @@ -543,6 +609,8 @@ void FileProviderDomainManager::setupFileProviderDomains() void FileProviderDomainManager::updateFileProviderDomains() { + qCDebug(lcMacFileProviderDomainManager) << "Updating file provider domains."; + if (!d) { return; } @@ -558,16 +626,23 @@ void FileProviderDomainManager::updateFileProviderDomains() } if (const auto accountState = AccountManager::instance()->accountFromUserId(accountUserIdAtHost)) { + qCDebug(lcMacFileProviderDomainManager) << "Succeed in fetching account state by account id" + << accountUserIdAtHost + << ", adding file provider domain for account."; + addFileProviderDomainForAccount(accountState.data()); } else { - qCWarning(lcMacFileProviderDomainManager) << "Could not find account for file provider domain:" << accountUserIdAtHost - << "removing account from list of vfs-enabled accounts."; + qCWarning(lcMacFileProviderDomainManager) << "Could not fetch account state by account id" + << accountUserIdAtHost + << ", removing account from list of VFS-enabled accounts."; + FileProviderSettingsController::instance()->setVfsEnabledForAccount(accountUserIdAtHost, false); } } for (const auto &remainingDomainUserId : configuredDomains) { - const auto accountState = AccountManager::instance()->accountFromUserId(remainingDomainUserId); + const auto accountId = accountIdFromDomainId(remainingDomainUserId); + const auto accountState = AccountManager::instance()->accountFromUserId(accountId); removeFileProviderDomainForAccount(accountState.data()); } @@ -697,7 +772,7 @@ AccountStatePtr FileProviderDomainManager::accountStateFromFileProviderDomainIde QString FileProviderDomainManager::fileProviderDomainIdentifierFromAccountState(const AccountStatePtr &accountState) { - return domainIdentifierForAccount(accountState->account()); + return uuidDomainIdentifierForAccount(accountState->account()); } void* FileProviderDomainManager::domainForAccount(const AccountState * const accountState) diff --git a/src/gui/macOS/fileprovidersettingscontroller_mac.mm b/src/gui/macOS/fileprovidersettingscontroller_mac.mm index bf84c557fc..3bf0af924c 100644 --- a/src/gui/macOS/fileprovidersettingscontroller_mac.mm +++ b/src/gui/macOS/fileprovidersettingscontroller_mac.mm @@ -66,9 +66,14 @@ public: { QStringList qEnabledAccounts; NSArray *const enabledAccounts = nsEnabledAccounts(); + for (NSString *const userIdAtHostString in enabledAccounts) { + qCDebug(lcFileProviderSettingsController) << "Found VFS-enabled account in user defaults:" + << userIdAtHostString; + qEnabledAccounts.append(QString::fromNSString(userIdAtHostString)); } + return qEnabledAccounts; } @@ -231,13 +236,14 @@ public slots: private slots: void updateDomainSyncStatuses() { - qCInfo(lcFileProviderSettingsController) << "Updating domain sync statuses"; + qCInfo(lcFileProviderSettingsController) << "Updating file provider domain sync statuses."; _fileProviderDomainSyncStatuses.clear(); const auto enabledAccounts = nsEnabledAccounts(); - for (NSString *const domainIdentifier in enabledAccounts) { - const auto qDomainIdentifier = QString::fromNSString(domainIdentifier); - const auto syncStatus = new FileProviderDomainSyncStatus(qDomainIdentifier, q); - _fileProviderDomainSyncStatuses.insert(qDomainIdentifier, syncStatus); + + for (NSString *const accountIdentifier in enabledAccounts) { + const auto domainIdentifier = FileProviderUtils::domainIdentifierForAccountIdentifier(accountIdentifier); + const auto syncStatus = new FileProviderDomainSyncStatus(domainIdentifier, q); + _fileProviderDomainSyncStatuses.insert(domainIdentifier, syncStatus); } } @@ -249,7 +255,7 @@ private: void fetchMaterialisedFilesStorageUsage() { - qCInfo(lcFileProviderSettingsController) << "Fetching materialised files storage usage"; + qCInfo(lcFileProviderSettingsController) << "Fetching used storage space of materialized items."; [NSFileProviderManager getDomainsWithCompletionHandler: ^(NSArray *const domains, NSError *const error) { if (error != nil) { @@ -377,37 +383,52 @@ void FileProviderSettingsController::setVfsEnabledForAccount(const QString &user bool FileProviderSettingsController::trashDeletionEnabledForAccount(const QString &userIdAtHost) const { const auto xpc = FileProvider::instance()->xpc(); + if (!xpc) { return true; } - if (const auto trashDeletionState = xpc->trashDeletionEnabledStateForExtension(userIdAtHost)) { + + const auto domainId = FileProviderUtils::domainIdentifierForAccountIdentifier(userIdAtHost); + + if (const auto trashDeletionState = xpc->trashDeletionEnabledStateForFileProviderDomain(domainId)) { return trashDeletionState->first; } + return true; } bool FileProviderSettingsController::trashDeletionSetForAccount(const QString &userIdAtHost) const { const auto xpc = FileProvider::instance()->xpc(); + if (!xpc) { return false; } - if (const auto state = xpc->trashDeletionEnabledStateForExtension(userIdAtHost)) { + + const auto domainId = FileProviderUtils::domainIdentifierForAccountIdentifier(userIdAtHost); + + if (const auto state = xpc->trashDeletionEnabledStateForFileProviderDomain(domainId)) { return state->second; } + return false; } void FileProviderSettingsController::setTrashDeletionEnabledForAccount(const QString &userIdAtHost, const bool setEnabled) { const auto xpc = FileProvider::instance()->xpc(); + if (!xpc) { // Reset state of UI elements emit trashDeletionEnabledForAccountChanged(userIdAtHost); emit trashDeletionSetForAccountChanged(userIdAtHost); return; } - xpc->setTrashDeletionEnabledForExtension(userIdAtHost, setEnabled); + + const auto domainId = FileProviderUtils::domainIdentifierForAccountIdentifier(userIdAtHost); + + xpc->setTrashDeletionEnabledForFileProviderDomain(domainId, setEnabled); + emit trashDeletionEnabledForAccountChanged(userIdAtHost); emit trashDeletionSetForAccountChanged(userIdAtHost); } @@ -502,7 +523,7 @@ void FileProviderSettingsController::createDebugArchive(const QString &userIdAtH qCWarning(lcFileProviderSettingsController) << "Could not create debug archive, FileProviderXPC is not available."; return; } - xpc->createDebugArchiveForExtension(userIdAtHost, filename); + xpc->createDebugArchiveForFileProviderDomain(userIdAtHost, filename); } FileProviderDomainSyncStatus *FileProviderSettingsController::domainSyncStatusForAccount(const QString &userIdAtHost) const diff --git a/src/gui/macOS/fileproviderutils.h b/src/gui/macOS/fileproviderutils.h index ecebbb744d..a2b56812ad 100644 --- a/src/gui/macOS/fileproviderutils.h +++ b/src/gui/macOS/fileproviderutils.h @@ -5,6 +5,8 @@ #pragma once +#include "accountstate.h" + class QString; @class NSFileProviderDomain; @@ -12,7 +14,7 @@ class QString; /** * This file contains the FileProviderUtils namespace, which contains - * utility functions for the FileProvider extension. + * utility functions for the file provider extension. * * Unlike other classes or namespaces in this module, this does not have * a clear file separation between C++ and Objective C++ code. @@ -32,9 +34,82 @@ namespace Mac { namespace FileProviderUtils { +static constexpr auto bundleExtensions = std::array{ + QLatin1StringView(".app"), + QLatin1StringView(".framework"), + QLatin1StringView(".kext"), + QLatin1StringView(".plugin"), + QLatin1StringView(".docset"), + QLatin1StringView(".xpc"), + QLatin1StringView(".qlgenerator"), + QLatin1StringView(".component"), + QLatin1StringView(".saver"), + QLatin1StringView(".mdimporter") +}; + +static const QRegularExpression illegalChars("[:/]"); + +/** + * @brief Synchronously retrieves an NSFileProviderDomain for the given domain identifier. + * + * This function searches through all registered file provider domains and returns the one + * matching the provided identifier. The function blocks until the asynchronous file provider + * API call completes using a dispatch semaphore. + * + * @param domainIdentifier The unique identifier of the domain to find + * @return A retained NSFileProviderDomain object if found, nil otherwise + * + * @warning The returned NSFileProviderDomain has been retained and MUST be released by the caller! + * @warning This function blocks the calling thread and should not be called frequently + * due to its synchronous nature over an asynchronous API + */ // Synchronous function to get the domain for a domain identifier NSFileProviderDomain *domainForIdentifier(const QString &domainIdentifier); +/** + * @brief Resolve a file provider domain identifier by the given account. + * + * This function checks the list of known identifiers to contain one for the given account + * and falls back to deterministic legacy identifiers derived from the account identifier. + */ +QString domainIdentifierForAccount(const OCC::Account * const account); + +/** + * @brief Resolve a file provider domain identifier by the given account pointer. + */ +QString domainIdentifierForAccount(const OCC::AccountPtr account); + +/** + * @brief Resolve a file provider domain identifier by the given account identifier. + */ +QString domainIdentifierForAccountIdentifier(const QString &accountId); + +/** + * @brief Resolve a file provider domain identifier by the given account identifier. + */ +QString domainIdentifierForAccountIdentifier(const NSString *accountId); + +/** + * @brief Whether the given domain identifier contains illegal characters or a known bundle extension. + */ +bool illegalDomainIdentifier(const QString &domainId); + +/** + * @brief Synchronously retrieves an NSFileProviderManager for the given domain identifier. + * + * This function first finds the NSFileProviderDomain using domainForIdentifier, then + * creates and returns the corresponding NSFileProviderManager. The function handles + * proper memory management by releasing the intermediate domain object. + * + * @param domainIdentifier The unique identifier of the domain to get the manager for + * @return An NSFileProviderManager object for the domain if found, nil otherwise + * + * @warning This function blocks the calling thread due to its dependency on domainForIdentifier + * @warning The caller does NOT need to release the returned NSFileProviderManager + * (it follows standard Objective-C memory management) + * + * @see domainForIdentifier for domain lookup implementation details + */ // Synchronous function to get manager for a domain identifier NSFileProviderManager *managerForDomainIdentifier(const QString &domainIdentifier); diff --git a/src/gui/macOS/fileproviderutils_mac.mm b/src/gui/macOS/fileproviderutils_mac.mm index 8ec3de4b0b..7f664c1654 100644 --- a/src/gui/macOS/fileproviderutils_mac.mm +++ b/src/gui/macOS/fileproviderutils_mac.mm @@ -3,9 +3,12 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ +#include "configfile.h" #include "fileproviderutils.h" +#include "account.h" #include +#include #include #import @@ -18,6 +21,18 @@ namespace FileProviderUtils { Q_LOGGING_CATEGORY(lcMacFileProviderUtils, "nextcloud.gui.macfileproviderutils", QtInfoMsg) +inline bool hasBundleExtension(const QString &domainId) +{ + return std::any_of(bundleExtensions.begin(), bundleExtensions.end(), [&domainId](const auto &ext) { + return domainId.endsWith(ext); + }); +} + +bool illegalDomainIdentifier(const QString &domainId) +{ + return !domainId.isEmpty() && !illegalChars.match(domainId).hasMatch() && hasBundleExtension(domainId); +} + NSFileProviderDomain *domainForIdentifier(const QString &domainIdentifier) { __block NSFileProviderDomain *foundDomain = nil; @@ -30,13 +45,16 @@ NSFileProviderDomain *domainForIdentifier(const QString &domainIdentifier) [NSFileProviderManager getDomainsWithCompletionHandler:^(NSArray *const domains, NSError *const error) { if (error != nil) { - qCWarning(lcMacFileProviderUtils) << "Error fetching domains:" + qCWarning(lcMacFileProviderUtils) << "Error fetching file provider domains:" << error.localizedDescription; dispatch_semaphore_signal(semaphore); return; } for (NSFileProviderDomain *const domain in domains) { + qCInfo(lcMacFileProviderUtils) << "Found file provider domain with identifier:" + << domain.identifier; + if ([domain.identifier isEqualToString:nsDomainIdentifier]) { [domain retain]; foundDomain = domain; @@ -51,13 +69,71 @@ NSFileProviderDomain *domainForIdentifier(const QString &domainIdentifier) dispatch_release(semaphore); if (foundDomain == nil) { - qCWarning(lcMacFileProviderUtils) << "No matching item domain for identifier" + qCWarning(lcMacFileProviderUtils) << "No matching file provider domain found for identifier: " << domainIdentifier; } return foundDomain; } +QString domainIdentifierForAccountIdentifier(const QString &accountId) +{ + qCDebug(lcMacFileProviderUtils) << "Resolving file provider domain identifier by account id:" + << accountId; + + OCC::ConfigFile cfg; + const QString existingUuid = cfg.fileProviderDomainUuidFromAccountId(accountId); + + if (!existingUuid.isEmpty()) { + qCDebug(lcMacFileProviderUtils) << "Found existing file provider domain UUID to return:" + << existingUuid + << "for account id:" + << accountId; + + return existingUuid; + } + + auto domainId = accountId; + Q_ASSERT(!domainId.isEmpty()); + + domainId.replace(illegalChars, "-"); + + // Some url domains like .app cause issues on macOS as these are also bundle extensions. + // Under the hood, fileproviderd will create a folder for the user to access the files named + // after the domain identifier. If the url domain is the same as a bundle extension, Finder + // will interpret this folder as a bundle and will not allow the user to access the files. + // Here we wrap the dot in the url domain extension to prevent this from happening. + for (const auto &ext : bundleExtensions) { + if (domainId.endsWith(ext)) { + domainId = domainId.left(domainId.length() - ext.length()); + domainId += "(.)" + ext.right(ext.length() - 1); + break; + } + } + + return domainId; +} + +QString domainIdentifierForAccountIdentifier(const NSString *accountId) +{ + const auto qAccountId = QString::fromNSString(accountId); + + return domainIdentifierForAccountIdentifier(qAccountId); +} + +QString domainIdentifierForAccount(const OCC::Account * const account) +{ + Q_ASSERT(account); + auto accountId = account->userIdAtHostWithPort(); + + return domainIdentifierForAccountIdentifier(accountId); +} + +QString domainIdentifierForAccount(const OCC::AccountPtr account) +{ + return domainIdentifierForAccount(account.get()); +} + NSFileProviderManager *managerForDomainIdentifier(const QString &domainIdentifier) { NSFileProviderDomain * const domain = domainForIdentifier(domainIdentifier); diff --git a/src/gui/macOS/fileproviderxpc.h b/src/gui/macOS/fileproviderxpc.h index 6787fdddc3..664c4ff48d 100644 --- a/src/gui/macOS/fileproviderxpc.h +++ b/src/gui/macOS/fileproviderxpc.h @@ -14,10 +14,10 @@ namespace OCC::Mac { /* - * Establishes communication between the app and the file provider extension. - * This is done via File Provider's XPC services API. - * Note that this is for client->extension communication, not the other way around. - * This is because the extension does not have a way to communicate with the client through the File Provider XPC API + * Establishes communication between the app and the file provider extension processes. + * This is done via services exposed by the file provider extension through XPC. + * Note that this is for desktop client app to file provider extension communication only, not the other way around. + * This is because the extension does not have a way to communicate with the client through XPC. */ class FileProviderXPC : public QObject { @@ -26,26 +26,26 @@ class FileProviderXPC : public QObject public: explicit FileProviderXPC(QObject *parent = nullptr); - [[nodiscard]] bool fileProviderExtReachable(const QString &extensionAccountId, bool retry = true, bool reconfigureOnFail = true); + [[nodiscard]] bool fileProviderDomainReachable(const QString &fileProviderDomainIdentifier, bool retry = true, bool reconfigureOnFail = true); - [[nodiscard]] std::optional> trashDeletionEnabledStateForExtension(const QString &extensionAccountId) const; + [[nodiscard]] std::optional> trashDeletionEnabledStateForFileProviderDomain(const QString &fileProviderDomainIdentifier) const; public slots: - void connectToExtensions(); - void configureExtensions(); - void authenticateExtension(const QString &extensionAccountId) const; - void unauthenticateExtension(const QString &extensionAccountId) const; - void createDebugArchiveForExtension(const QString &extensionAccountId, const QString &filename); + void connectToFileProviderDomains(); + void authenticateFileProviderDomains(); + void authenticateFileProviderDomain(const QString &fileProviderDomainIdentifier) const; + void unauthenticateFileProviderDomain(const QString &fileProviderDomainIdentifier) const; + void createDebugArchiveForFileProviderDomain(const QString &fileProviderDomainIdentifier, const QString &filename); void setIgnoreList() const; - void setTrashDeletionEnabledForExtension(const QString &extensionAccountId, bool enabled) const; + void setTrashDeletionEnabledForFileProviderDomain(const QString &fileProviderDomainIdentifier, bool enabled) const; private slots: void slotAccountStateChanged(AccountState::State state) const; private: QHash _clientCommServices; - QHash _unreachableAccountExtensions; + QHash _unreachableFileProviderDomains; }; } // namespace OCC::Mac diff --git a/src/gui/macOS/fileproviderxpc_mac.mm b/src/gui/macOS/fileproviderxpc_mac.mm index 1239b2db39..a8ff94f88f 100644 --- a/src/gui/macOS/fileproviderxpc_mac.mm +++ b/src/gui/macOS/fileproviderxpc_mac.mm @@ -33,29 +33,31 @@ FileProviderXPC::FileProviderXPC(QObject *parent) { } -void FileProviderXPC::connectToExtensions() +void FileProviderXPC::connectToFileProviderDomains() { - qCInfo(lcFileProviderXPC) << "Starting file provider XPC"; + qCInfo(lcFileProviderXPC) << "Connecting to file provider domains."; + const auto managers = FileProviderXPCUtils::getDomainManagers(); const auto fpServices = FileProviderXPCUtils::getFileProviderServices(managers); const auto connections = FileProviderXPCUtils::connectToFileProviderServices(fpServices); _clientCommServices = FileProviderXPCUtils::processClientCommunicationConnections(connections); } -void FileProviderXPC::configureExtensions() +void FileProviderXPC::authenticateFileProviderDomains() { - for (const auto &extensionNcAccount : _clientCommServices.keys()) { - qCInfo(lcFileProviderXPC) << "Sending message to client communication service"; - authenticateExtension(extensionNcAccount); + for (const auto &fileProviderDomainIdentifier : _clientCommServices.keys()) { + qCInfo(lcFileProviderXPC) << "Authenticating file provider domains."; + authenticateFileProviderDomain(fileProviderDomainIdentifier); } } -void FileProviderXPC::authenticateExtension(const QString &extensionAccountId) const +void FileProviderXPC::authenticateFileProviderDomain(const QString &fileProviderDomainIdentifier) const { - const auto accountState = FileProviderDomainManager::accountStateFromFileProviderDomainIdentifier(extensionAccountId); + const auto accountState = FileProviderDomainManager::accountStateFromFileProviderDomainIdentifier(fileProviderDomainIdentifier); + if (!accountState) { - qCWarning(lcFileProviderXPC) << "Account state is null for received account" - << extensionAccountId; + qCWarning(lcFileProviderXPC) << "Account state is null for file provider domain to authenticate" + << fileProviderDomainIdentifier; return; } @@ -67,9 +69,20 @@ void FileProviderXPC::authenticateExtension(const QString &extensionAccountId) c NSString *const userId = account->davUser().toNSString(); NSString *const serverUrl = account->url().toString().toNSString(); NSString *const password = credentials->password().toNSString(); + NSString *const passwordDescription = password.length > 0 ? @"SOME PASSWORD" : @"EMPTY PASSWORD"; NSString *const userAgent = QString::fromUtf8(Utility::userAgentString()).toNSString(); - const auto clientCommService = (NSObject *)_clientCommServices.value(extensionAccountId); + const auto clientCommService = (NSObject *)_clientCommServices.value(fileProviderDomainIdentifier); + + qCInfo(lcFileProviderXPC) << "Authenticating file provider domain with identifier" + << fileProviderDomainIdentifier + << "as" + << user + << "on" + << serverUrl + << "with" + << passwordDescription; + [clientCommService configureAccountWithUser:user userId:userId serverUrl:serverUrl @@ -77,10 +90,10 @@ void FileProviderXPC::authenticateExtension(const QString &extensionAccountId) c userAgent:userAgent]; } -void FileProviderXPC::unauthenticateExtension(const QString &extensionAccountId) const +void FileProviderXPC::unauthenticateFileProviderDomain(const QString &fileProviderDomainIdentifier) const { - qCInfo(lcFileProviderXPC) << "Unauthenticating extension" << extensionAccountId; - const auto clientCommService = (NSObject *)_clientCommServices.value(extensionAccountId); + qCInfo(lcFileProviderXPC) << "Unauthenticating file provider domain with identifier" << fileProviderDomainIdentifier; + const auto clientCommService = (NSObject *)_clientCommServices.value(fileProviderDomainIdentifier); [clientCommService removeAccountConfig]; } @@ -103,24 +116,26 @@ void FileProviderXPC::slotAccountStateChanged(const AccountState::State state) c case AccountState::RedirectDetected: case AccountState::NeedToSignTermsOfService: // Notify File Provider that it should show the not authenticated message - unauthenticateExtension(extensionAccountId); + unauthenticateFileProviderDomain(extensionAccountId); break; case AccountState::Connected: // Provide credentials - authenticateExtension(extensionAccountId); + authenticateFileProviderDomain(extensionAccountId); break; } } -void FileProviderXPC::createDebugArchiveForExtension(const QString &extensionAccountId, const QString &filename) +void FileProviderXPC::createDebugArchiveForFileProviderDomain(const QString &fileProviderDomainIdentifier, const QString &filename) { - qCInfo(lcFileProviderXPC) << "Creating debug archive for extension" << extensionAccountId << "at" << filename; - if (!fileProviderExtReachable(extensionAccountId)) { + qCInfo(lcFileProviderXPC) << "Creating debug archive for extension" << fileProviderDomainIdentifier << "at" << filename; + + if (!fileProviderDomainReachable(fileProviderDomainIdentifier)) { qCWarning(lcFileProviderXPC) << "Extension is not reachable. Cannot create debug archive"; return; } + // You need to fetch the contents from the extension and then create the archive from the client side. // The extension is not allowed to ask for permission to write into the file system as it is not a user facing process. - const auto clientCommService = (NSObject *)_clientCommServices.value(extensionAccountId); + const auto clientCommService = (NSObject *)_clientCommServices.value(fileProviderDomainIdentifier); const auto group = dispatch_group_create(); __block NSString *rcvdDebugLogString; dispatch_group_enter(group); @@ -152,43 +167,42 @@ void FileProviderXPC::createDebugArchiveForExtension(const QString &extensionAcc [rcvdDebugLogString release]; } -bool FileProviderXPC::fileProviderExtReachable(const QString &extensionAccountId, const bool retry, const bool reconfigureOnFail) +bool FileProviderXPC::fileProviderDomainReachable(const QString &fileProviderDomainIdentifier, const bool retry, const bool reconfigureOnFail) { - const auto lastUnreachableTime = _unreachableAccountExtensions.value(extensionAccountId); - if (!retry + const auto lastUnreachableTime = _unreachableFileProviderDomains.value(fileProviderDomainIdentifier); + if (!retry && !reconfigureOnFail && lastUnreachableTime.isValid() && lastUnreachableTime.secsTo(QDateTime::currentDateTime()) < ::reachableRetryTimeout) { - qCInfo(lcFileProviderXPC) << "File provider extension was unreachable less than a minute ago. " - << "Not checking again"; + qCInfo(lcFileProviderXPC) << "File provider extension was unreachable less than a minute ago. Not checking again."; return false; } - const auto service = (NSObject *)_clientCommServices.value(extensionAccountId); + const auto service = (NSObject *)_clientCommServices.value(fileProviderDomainIdentifier); if (service == nil) { - qCWarning(lcFileProviderXPC) << "Could not get service for extension" << extensionAccountId; + qCWarning(lcFileProviderXPC) << "Could not get service for file provider domain" << fileProviderDomainIdentifier; return false; } __block auto response = false; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - [service getExtensionAccountIdWithCompletionHandler:^(NSString *const, NSError *const) { + [service getFileProviderDomainIdentifierWithCompletionHandler:^(NSString *const, NSError *const) { response = true; dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, semaphoreWaitDelta)); if (response) { - _unreachableAccountExtensions.remove(extensionAccountId); + _unreachableFileProviderDomains.remove(fileProviderDomainIdentifier); } else { - qCWarning(lcFileProviderXPC) << "Could not reach file provider extension."; - + qCWarning(lcFileProviderXPC) << "Could not reach file provider domain service."; + if (reconfigureOnFail) { - qCWarning(lcFileProviderXPC) << "Could not reach extension" - << extensionAccountId + qCWarning(lcFileProviderXPC) << "Could not reach service of file provider domain" + << fileProviderDomainIdentifier << "going to attempt reconfiguring interface"; const auto ncDomainManager = FileProvider::instance()->domainManager(); - const auto accountState = ncDomainManager->accountStateFromFileProviderDomainIdentifier(extensionAccountId); + const auto accountState = ncDomainManager->accountStateFromFileProviderDomainIdentifier(fileProviderDomainIdentifier); const auto domain = (NSFileProviderDomain *)(ncDomainManager->domainForAccount(accountState.get())); const auto manager = [NSFileProviderManager managerForDomain:domain]; const auto fpServices = FileProviderXPCUtils::getFileProviderServices(@[manager]); @@ -198,21 +212,22 @@ bool FileProviderXPC::fileProviderExtReachable(const QString &extensionAccountId } if (retry) { - qCWarning(lcFileProviderXPC) << "Could not reach extension" << extensionAccountId << "retrying"; - return fileProviderExtReachable(extensionAccountId, false, false); + qCWarning(lcFileProviderXPC) << "Could not reach file provider domain" << fileProviderDomainIdentifier << ", retrying."; + return fileProviderDomainReachable(fileProviderDomainIdentifier, false, false); } else { - _unreachableAccountExtensions.insert(extensionAccountId, QDateTime::currentDateTime()); + _unreachableFileProviderDomains.insert(fileProviderDomainIdentifier, QDateTime::currentDateTime()); } } return response; } -std::optional> FileProviderXPC::trashDeletionEnabledStateForExtension(const QString &extensionAccountId) const +std::optional> FileProviderXPC::trashDeletionEnabledStateForFileProviderDomain(const QString &fileProviderDomainIdentifier) const { - qCInfo(lcFileProviderXPC) << "Checking if fast enumeration is enabled for extension" << extensionAccountId; - const auto service = (NSObject *)_clientCommServices.value(extensionAccountId); + qCInfo(lcFileProviderXPC) << "Checking if fast enumeration is enabled for file provider domain" << fileProviderDomainIdentifier; + const auto service = (NSObject *)_clientCommServices.value(fileProviderDomainIdentifier); + if (service == nil) { - qCWarning(lcFileProviderXPC) << "Could not get service for extension" << extensionAccountId; + qCWarning(lcFileProviderXPC) << "Could not get service for file provider domain" << fileProviderDomainIdentifier; return std::nullopt; } @@ -234,10 +249,10 @@ std::optional> FileProviderXPC::trashDeletionEnabledStateF return std::optional>{{receivedTrashDeletionEnabled, receivedTrashDeletionEnabledSet}}; } -void FileProviderXPC::setTrashDeletionEnabledForExtension(const QString &extensionAccountId, bool enabled) const +void FileProviderXPC::setTrashDeletionEnabledForFileProviderDomain(const QString &fileProviderDomainIdentifier, bool enabled) const { - qCInfo(lcFileProviderXPC) << "Setting trash deletion enabled for extension" << extensionAccountId << "to" << enabled; - const auto service = (NSObject *)_clientCommServices.value(extensionAccountId); + qCInfo(lcFileProviderXPC) << "Setting trash deletion enabled for file provider domain" << fileProviderDomainIdentifier << "to" << enabled; + const auto service = (NSObject *)_clientCommServices.value(fileProviderDomainIdentifier); [service setTrashDeletionEnabled:enabled]; } @@ -250,14 +265,16 @@ void FileProviderXPC::setIgnoreList() const qCInfo(lcFileProviderXPC) << "Updating ignore list with" << qPatterns.size() << "patterns"; const auto mutableNsPatterns = NSMutableArray.array; + for (const auto &pattern : qPatterns) { [mutableNsPatterns addObject:pattern.toNSString()]; } + NSArray *const nsPatterns = [mutableNsPatterns copy]; - for (const auto &extensionAccountId : _clientCommServices.keys()) { - qCInfo(lcFileProviderXPC) << "Updating ignore list for extension" << extensionAccountId; - const auto service = (NSObject *)_clientCommServices.value(extensionAccountId); + for (const auto &fileProviderDomainIdentifier : _clientCommServices.keys()) { + qCInfo(lcFileProviderXPC) << "Updating ignore list of file provider domain" << fileProviderDomainIdentifier; + const auto service = (NSObject *)_clientCommServices.value(fileProviderDomainIdentifier); [service setIgnoreList:nsPatterns]; } } diff --git a/src/gui/macOS/fileproviderxpc_mac_utils.h b/src/gui/macOS/fileproviderxpc_mac_utils.h index e79e9afb1d..746666a5cc 100644 --- a/src/gui/macOS/fileproviderxpc_mac_utils.h +++ b/src/gui/macOS/fileproviderxpc_mac_utils.h @@ -19,7 +19,12 @@ NSArray *> *get NSArray *connectToFileProviderServices(NSArray *> *fpServices); void configureFileProviderConnection(NSXPCConnection *connection); NSObject *getRemoteServiceObject(NSXPCConnection *connection, Protocol *protocol); -NSString *getExtensionAccountId(NSObject *clientCommService); + +/** + * @brief Get the domain identifier for and from a given client communication service. + */ +NSString *getFileProviderDomainIdentifier(NSObject *clientCommService); + QHash processClientCommunicationConnections(NSArray *connections); -} \ No newline at end of file +} diff --git a/src/gui/macOS/fileproviderxpc_mac_utils.mm b/src/gui/macOS/fileproviderxpc_mac_utils.mm index fa0fe42fc3..2e2edb7155 100644 --- a/src/gui/macOS/fileproviderxpc_mac_utils.mm +++ b/src/gui/macOS/fileproviderxpc_mac_utils.mm @@ -26,15 +26,16 @@ NSArray *getDomainManagers() dispatch_group_enter(group); // Set up connections for each domain - [NSFileProviderManager getDomainsWithCompletionHandler:^(NSArray *const domains, NSError *const error){ + [NSFileProviderManager getDomainsWithCompletionHandler:^(NSArray *const domains, NSError *const error) { if (error != nil) { - qCWarning(lcFileProviderXPCUtils) << "Error getting domains" << error; + qCWarning(lcFileProviderXPCUtils) << "Error getting file provider domains" << error; dispatch_group_leave(group); return; } for (NSFileProviderDomain *const domain in domains) { - qCInfo(lcFileProviderXPCUtils) << "Got domain" << domain.identifier; + qCInfo(lcFileProviderXPCUtils) << "Found file provider domain" << domain.identifier; + NSFileProviderManager *const manager = [NSFileProviderManager managerForDomain:domain]; if (manager) { [managers addObject:manager]; @@ -49,7 +50,7 @@ NSArray *getDomainManagers() dispatch_group_wait(group, DISPATCH_TIME_FOREVER); if (managers.count == 0) { - qCWarning(lcFileProviderXPCUtils) << "No domains found"; + qCWarning(lcFileProviderXPCUtils) << "No file provider domains found!"; } return managers.copy; @@ -223,23 +224,28 @@ NSObject *getRemoteServiceObject(NSXPCConnection *const connection, Protocol *co return remoteServiceObject; } -NSString *getExtensionAccountId(NSObject *const clientCommService) +NSString *getFileProviderDomainIdentifier(NSObject *const clientCommService) { Q_ASSERT(clientCommService != nil); - __block NSString *extensionNcAccount; + __block NSString *domainIdentifier; dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); - [clientCommService getExtensionAccountIdWithCompletionHandler:^(NSString *const extensionAccountId, NSError *const error){ + + [clientCommService getFileProviderDomainIdentifierWithCompletionHandler:^(NSString *const extensionAccountId, NSError *const error){ if (error != nil) { - qCWarning(lcFileProviderXPCUtils) << "Error getting extension account id" << error; + qCWarning(lcFileProviderXPCUtils) << "Error getting domain id from file provider service" << error; dispatch_group_leave(group); + return; } - extensionNcAccount = [[NSString alloc] initWithString:extensionAccountId]; + + domainIdentifier = [[NSString alloc] initWithString:extensionAccountId]; dispatch_group_leave(group); }]; + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); - return extensionNcAccount; + + return domainIdentifier; } QHash processClientCommunicationConnections(NSArray *const connections) @@ -252,19 +258,26 @@ QHash processClientCommunicationConnections(NSArray *)getRemoteServiceObject(connection, remoteObjectInterfaceProtocol); + if (clientCommService == nil) { qCWarning(lcFileProviderXPCUtils) << "Client communication service is nil"; continue; } + [clientCommService retain]; - const auto extensionNcAccount = getExtensionAccountId(clientCommService); - if (extensionNcAccount == nil) { - qCWarning(lcFileProviderXPCUtils) << "Extension account id is nil"; + const auto domainIdentifier = getFileProviderDomainIdentifier(clientCommService); + + if (domainIdentifier == nil) { + qCWarning(lcFileProviderXPCUtils) << "Could not retrieve domain id from file provider service"; continue; } - qCInfo(lcFileProviderXPCUtils) << "Got extension account id" << extensionNcAccount.UTF8String; - clientCommServices.insert(QString::fromNSString(extensionNcAccount), clientCommService); + + qCInfo(lcFileProviderXPCUtils) << "Got domain id" + << domainIdentifier.UTF8String + << "from file provider service"; + + clientCommServices.insert(QString::fromNSString(domainIdentifier), clientCommService); } return clientCommServices; diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index dc78bd0f1a..c20e040f45 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -316,7 +316,7 @@ void ownCloudGui::slotComputeOverallSyncStatus() allPaused = false; const auto fileProvider = Mac::FileProvider::instance(); - if (!fileProvider->xpc()->fileProviderExtReachable(accountFpId)) { + if (!fileProvider->xpc()->fileProviderDomainReachable(accountFpId)) { problemFileProviderAccounts.append(accountFpId); } else { switch (fileProvider->socketServer()->latestReceivedSyncStatusForAccount(accountState->account())) { diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp index 0116b23901..ae3f87b04b 100644 --- a/src/gui/owncloudsetupwizard.cpp +++ b/src/gui/owncloudsetupwizard.cpp @@ -705,14 +705,11 @@ void OwncloudSetupWizard::slotAssistantFinished(int result) #ifdef BUILD_FILE_PROVIDER_MODULE if (Mac::FileProvider::fileProviderAvailable() && _ocWizard->useVirtualFileSync()) { Mac::FileProvider::instance()->domainManager()->addFileProviderDomainForAccount(account); + auto const accountId = account->account()->userIdAtHostWithPort(); // let the user settings know that VFS is enabled - Mac::FileProviderSettingsController::instance()->setVfsEnabledForAccount( - Mac::FileProviderDomainManager::fileProviderDomainIdentifierFromAccountState(AccountStatePtr(account)), - true, - false - ); + Mac::FileProviderSettingsController::instance()->setVfsEnabledForAccount(accountId, true, false); _ocWizard->appendToConfigurationLog( - tr("File Provider-based account %1 successfully created!").arg(account->account()->userIdAtHostWithPort())); + tr("File Provider-based account %1 successfully created!").arg(accountId)); _ocWizard->done(result); emit ownCloudWizardDone(result); diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 17a05c61f0..d512fba3af 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -1304,4 +1304,41 @@ void ConfigFile::setDiscoveredLegacyConfigPath(const QString &discoveredLegacyCo _discoveredLegacyConfigPath = discoveredLegacyConfigPath; } +QString ConfigFile::fileProviderDomainUuidFromAccountId(const QString &accountId) const +{ + if (accountId.isEmpty()) { + return {}; + } + return retrieveData(QStringLiteral("FileProviderDomainUuids"), accountId).toString(); +} + +void ConfigFile::setFileProviderDomainUuidForAccountId(const QString &accountId, const QString &domainUuid) +{ + if (accountId.isEmpty() || domainUuid.isEmpty()) { + return; + } + storeData(QStringLiteral("FileProviderDomainUuids"), accountId, domainUuid); + storeData(QStringLiteral("FileProviderAccountIds"), domainUuid, accountId); +} + +QString ConfigFile::accountIdFromFileProviderDomainUuid(const QString &domainUuid) const +{ + if (domainUuid.isEmpty()) { + return {}; + } + return retrieveData(QStringLiteral("FileProviderAccountIds"), domainUuid).toString(); +} + +void ConfigFile::removeFileProviderDomainUuidMapping(const QString &accountId) +{ + if (accountId.isEmpty()) { + return; + } + const QString domainUuid = fileProviderDomainUuidFromAccountId(accountId); + if (!domainUuid.isEmpty()) { + removeData(QStringLiteral("FileProviderAccountIds"), domainUuid); + } + removeData(QStringLiteral("FileProviderDomainUuids"), accountId); +} + } diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index b5e67561ef..c2b22f0c6e 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -246,6 +246,12 @@ public: [[nodiscard]] static QString discoveredLegacyConfigPath(); static void setDiscoveredLegacyConfigPath(const QString &discoveredLegacyConfigPath); + /// File Provider Domain UUID to Account ID mapping + [[nodiscard]] QString fileProviderDomainUuidFromAccountId(const QString &accountId) const; + void setFileProviderDomainUuidForAccountId(const QString &accountId, const QString &domainUuid); + [[nodiscard]] QString accountIdFromFileProviderDomainUuid(const QString &domainUuid) const; + void removeFileProviderDomainUuidMapping(const QString &accountId); + static constexpr char isVfsEnabledC[] = "isVfsEnabled"; static constexpr char launchOnSystemStartupC[] = "launchOnSystemStartup"; static constexpr char optionalServerNotificationsC[] = "optionalServerNotifications";