mirror of
https://github.com/nextcloud/desktop.git
synced 2025-10-26 11:17:43 +00:00
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:
parent
2fdecc49ad
commit
853d1ab78b
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
"location" : "https://github.com/nextcloud/NextcloudFileProviderKit.git",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "3e00b22a199ae01d8f70438a854af79e0046b672"
|
||||
"revision" : "e2633ed6dddebd0abe5735d59dbcaaeaebc0acae"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -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 {};
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.";
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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())) {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user