feat: implement UUID-based file provider domain identifiers with backward compatibility.

Signed-off-by: Iva Horn <iva.horn@icloud.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-07-29 10:16:34 +00:00 committed by Iva Horn
parent 2fdecc49ad
commit 853d1ab78b
18 changed files with 530 additions and 202 deletions

View File

@ -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

View File

@ -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(

View File

@ -25,7 +25,7 @@
"location" : "https://github.com/nextcloud/NextcloudFileProviderKit.git",
"state" : {
"branch" : "main",
"revision" : "3e00b22a199ae01d8f70438a854af79e0046b672"
"revision" : "e2633ed6dddebd0abe5735d59dbcaaeaebc0acae"
}
},
{

View File

@ -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 {};
}

View File

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

View File

@ -80,8 +80,8 @@ void FileProvider::configureXPC()
_xpc = std::make_unique<FileProviderXPC>(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.";
}

View File

@ -9,11 +9,12 @@
#include <QLatin1StringView>
#include <QLoggingCategory>
#include <QRegularExpression>
#include <QUuid>
#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)

View File

@ -66,9 +66,14 @@ public:
{
QStringList qEnabledAccounts;
NSArray<NSString *> *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<NSFileProviderDomain *> *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

View File

@ -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);

View File

@ -3,9 +3,12 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "configfile.h"
#include "fileproviderutils.h"
#include "account.h"
#include <QLoggingCategory>
#include <QRegularExpression>
#include <QString>
#import <FileProvider/FileProvider.h>
@ -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<NSFileProviderDomain *> *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);

View File

@ -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<std::pair<bool, bool>> trashDeletionEnabledStateForExtension(const QString &extensionAccountId) const;
[[nodiscard]] std::optional<std::pair<bool, bool>> 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<QString, void*> _clientCommServices;
QHash<QString, QDateTime> _unreachableAccountExtensions;
QHash<QString, QDateTime> _unreachableFileProviderDomains;
};
} // namespace OCC::Mac

View File

@ -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<ClientCommunicationProtocol> *)_clientCommServices.value(extensionAccountId);
const auto clientCommService = (NSObject<ClientCommunicationProtocol> *)_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<ClientCommunicationProtocol> *)_clientCommServices.value(extensionAccountId);
qCInfo(lcFileProviderXPC) << "Unauthenticating file provider domain with identifier" << fileProviderDomainIdentifier;
const auto clientCommService = (NSObject<ClientCommunicationProtocol> *)_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<ClientCommunicationProtocol> *)_clientCommServices.value(extensionAccountId);
const auto clientCommService = (NSObject<ClientCommunicationProtocol> *)_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<ClientCommunicationProtocol> *)_clientCommServices.value(extensionAccountId);
const auto service = (NSObject<ClientCommunicationProtocol> *)_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<std::pair<bool, bool>> FileProviderXPC::trashDeletionEnabledStateForExtension(const QString &extensionAccountId) const
std::optional<std::pair<bool, bool>> FileProviderXPC::trashDeletionEnabledStateForFileProviderDomain(const QString &fileProviderDomainIdentifier) const
{
qCInfo(lcFileProviderXPC) << "Checking if fast enumeration is enabled for extension" << extensionAccountId;
const auto service = (NSObject<ClientCommunicationProtocol> *)_clientCommServices.value(extensionAccountId);
qCInfo(lcFileProviderXPC) << "Checking if fast enumeration is enabled for file provider domain" << fileProviderDomainIdentifier;
const auto service = (NSObject<ClientCommunicationProtocol> *)_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<std::pair<bool, bool>> FileProviderXPC::trashDeletionEnabledStateF
return std::optional<std::pair<bool, bool>>{{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<ClientCommunicationProtocol> *)_clientCommServices.value(extensionAccountId);
qCInfo(lcFileProviderXPC) << "Setting trash deletion enabled for file provider domain" << fileProviderDomainIdentifier << "to" << enabled;
const auto service = (NSObject<ClientCommunicationProtocol> *)_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<NSString *> *const nsPatterns = [mutableNsPatterns copy];
for (const auto &extensionAccountId : _clientCommServices.keys()) {
qCInfo(lcFileProviderXPC) << "Updating ignore list for extension" << extensionAccountId;
const auto service = (NSObject<ClientCommunicationProtocol> *)_clientCommServices.value(extensionAccountId);
for (const auto &fileProviderDomainIdentifier : _clientCommServices.keys()) {
qCInfo(lcFileProviderXPC) << "Updating ignore list of file provider domain" << fileProviderDomainIdentifier;
const auto service = (NSObject<ClientCommunicationProtocol> *)_clientCommServices.value(fileProviderDomainIdentifier);
[service setIgnoreList:nsPatterns];
}
}

View File

@ -19,7 +19,12 @@ NSArray<NSDictionary<NSFileProviderServiceName, NSFileProviderService *> *> *get
NSArray<NSXPCConnection *> *connectToFileProviderServices(NSArray<NSDictionary<NSFileProviderServiceName, NSFileProviderService *> *> *fpServices);
void configureFileProviderConnection(NSXPCConnection *connection);
NSObject *getRemoteServiceObject(NSXPCConnection *connection, Protocol *protocol);
NSString *getExtensionAccountId(NSObject<ClientCommunicationProtocol> *clientCommService);
/**
* @brief Get the domain identifier for and from a given client communication service.
*/
NSString *getFileProviderDomainIdentifier(NSObject<ClientCommunicationProtocol> *clientCommService);
QHash<QString, void*> processClientCommunicationConnections(NSArray<NSXPCConnection *> *connections);
}
}

View File

@ -26,15 +26,16 @@ NSArray<NSFileProviderManager *> *getDomainManagers()
dispatch_group_enter(group);
// Set up connections for each domain
[NSFileProviderManager getDomainsWithCompletionHandler:^(NSArray<NSFileProviderDomain *> *const domains, NSError *const error){
[NSFileProviderManager getDomainsWithCompletionHandler:^(NSArray<NSFileProviderDomain *> *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<NSFileProviderManager *> *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<ClientCommunicationProtocol> *const clientCommService)
NSString *getFileProviderDomainIdentifier(NSObject<ClientCommunicationProtocol> *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<QString, void*> processClientCommunicationConnections(NSArray<NSXPCConnection *> *const connections)
@ -252,19 +258,26 @@ QHash<QString, void*> processClientCommunicationConnections(NSArray<NSXPCConnect
configureFileProviderConnection(connection);
const auto clientCommService = (NSObject<ClientCommunicationProtocol> *)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;

View File

@ -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())) {

View File

@ -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("<font color=\"green\"><b>File Provider-based account %1 successfully created!</b></font>").arg(account->account()->userIdAtHostWithPort()));
tr("<font color=\"green\"><b>File Provider-based account %1 successfully created!</b></font>").arg(accountId));
_ocWizard->done(result);
emit ownCloudWizardDone(result);

View File

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

View File

@ -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";