diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift index e9f60607a5de6..61f0a06052fb5 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift @@ -132,4 +132,29 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte ) ncAccount = nil } + + func updatedSyncStateReporting(oldActions: Set) { + actionsLock.lock() + + guard oldActions.isEmpty != syncActions.isEmpty else { + actionsLock.unlock() + return + } + + let command = "FILE_PROVIDER_DOMAIN_SYNC_STATE_CHANGE" + var argument: String? + if oldActions.isEmpty, !syncActions.isEmpty { + argument = "SYNC_STARTED" + } else if !oldActions.isEmpty, syncActions.isEmpty { + argument = errorActions.isEmpty ? "SYNC_FINISHED" : "SYNC_FAILED" + errorActions = [] + } + + actionsLock.unlock() + + guard let argument else { return } + Logger.fileProviderExtension.debug("Reporting sync \(argument)") + let message = command + ":" + argument + "\n" + socketClient?.sendMessage(message) + } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+EnumerationListener.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+EnumerationListener.swift new file mode 100644 index 0000000000000..00a3fb2df5652 --- /dev/null +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+EnumerationListener.swift @@ -0,0 +1,23 @@ +// +// FileProviderExtension+EnumerationListener.swift +// FileProviderExt +// +// Created by Claudio Cambra on 16/7/24. +// + +import Foundation +import NextcloudFileProviderKit + +extension FileProviderExtension: EnumerationListener { + func enumerationActionStarted(actionId: UUID) { + insertSyncAction(actionId) + } + + func enumerationActionFinished(actionId: UUID) { + removeSyncAction(actionId) + } + + func enumerationActionFailed(actionId: UUID, error: Error) { + insertErrorAction(actionId) + } +} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift index 7d952f24e72b6..533d20d569e47 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift @@ -37,6 +37,10 @@ import OSLog return LocalSocketClient(socketPath: socketPath.path, lineProcessor: lineProcessor) }() + var syncActions = Set() + var errorActions = Set() + var actionsLock = NSLock() + // Whether or not we are going to recursively scan new folders when they are discovered. // Apple's recommendation is that we should always scan the file hierarchy fully. // This does lead to long load times when a file provider domain is initially configured. @@ -67,6 +71,32 @@ import OSLog ) } + func insertSyncAction(_ actionId: UUID) { + actionsLock.lock() + let oldActions = syncActions + syncActions.insert(actionId) + actionsLock.unlock() + updatedSyncStateReporting(oldActions: oldActions) + } + + func insertErrorAction(_ actionId: UUID) { + actionsLock.lock() + let oldActions = syncActions + syncActions.remove(actionId) + errorActions.insert(actionId) + actionsLock.unlock() + updatedSyncStateReporting(oldActions: oldActions) + } + + func removeSyncAction(_ actionId: UUID) { + actionsLock.lock() + let oldActions = syncActions + syncActions.remove(actionId) + errorActions.remove(actionId) + actionsLock.unlock() + updatedSyncStateReporting(oldActions: oldActions) + } + // MARK: - NSFileProviderReplicatedExtension protocol methods func item( @@ -88,6 +118,9 @@ import OSLog request: NSFileProviderRequest, completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void ) -> Progress { + let actionId = UUID() + insertSyncAction(actionId) + Logger.fileProviderExtension.debug( "Received request to fetch contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public)" ) @@ -97,6 +130,7 @@ import OSLog Logger.fileProviderExtension.error( "Can't return contents for a specific version as this is not supported." ) + insertErrorAction(actionId) completionHandler( nil, nil, @@ -112,6 +146,7 @@ import OSLog as account not set up yet. """ ) + insertErrorAction(actionId) completionHandler(nil, nil, NSFileProviderError(.notAuthenticated)) return Progress() } @@ -124,6 +159,7 @@ import OSLog """ ) completionHandler(nil, nil, NSFileProviderError(.noSuchItem)) + insertErrorAction(actionId) return Progress() } @@ -132,6 +168,7 @@ import OSLog let (localUrl, updatedItem, error) = await item.fetchContents( domain: self.domain, progress: progress ) + removeSyncAction(actionId) completionHandler(localUrl, updatedItem, error) } return progress @@ -147,6 +184,8 @@ import OSLog NSFileProviderItem?, NSFileProviderItemFields, Bool, Error? ) -> Void ) -> Progress { + let actionId = UUID() + insertSyncAction(actionId) let tempId = itemTemplate.itemIdentifier.rawValue Logger.fileProviderExtension.debug( @@ -163,6 +202,7 @@ import OSLog as account not set up yet """ ) + insertErrorAction(actionId) completionHandler( itemTemplate, NSFileProviderItemFields(), @@ -184,9 +224,14 @@ import OSLog ncAccount: ncAccount, progress: progress ) + if error != nil { + insertErrorAction(actionId) signalEnumerator(completionHandler: { _ in }) + } else { + removeSyncAction(actionId) } + completionHandler( item ?? itemTemplate, NSFileProviderItemFields(), @@ -210,6 +255,8 @@ import OSLog ) -> Progress { // An item was modified on disk, process the item's modification // TODO: Handle finder things like tags, other possible item changed fields + let actionId = UUID() + insertSyncAction(actionId) let identifier = item.itemIdentifier let ocId = identifier.rawValue @@ -224,6 +271,7 @@ import OSLog Logger.fileProviderExtension.error( "Not modifying item: \(ocId, privacy: .public) as account not set up yet." ) + insertErrorAction(actionId) completionHandler(item, [], false, NSFileProviderError(.notAuthenticated)) return Progress() } @@ -232,6 +280,7 @@ import OSLog Logger.fileProviderExtension.error( "Not modifying item: \(ocId, privacy: .public) as item not found." ) + insertErrorAction(actionId) completionHandler(item, [], false, NSFileProviderError(.noSuchItem)) return Progress() } @@ -249,9 +298,14 @@ import OSLog domain: domain, progress: progress ) + if error != nil { + insertErrorAction(actionId) signalEnumerator(completionHandler: { _ in }) + } else { + removeSyncAction(actionId) } + completionHandler(modifiedItem ?? item, [], false, error) } return progress @@ -264,6 +318,9 @@ import OSLog request _: NSFileProviderRequest, completionHandler: @escaping (Error?) -> Void ) -> Progress { + let actionId = UUID() + insertSyncAction(actionId) + Logger.fileProviderExtension.debug( "Received delete request for item: \(identifier.rawValue, privacy: .public)" ) @@ -272,6 +329,7 @@ import OSLog Logger.fileProviderExtension.error( "Not deleting item \(identifier.rawValue, privacy: .public), account not set up yet" ) + insertErrorAction(actionId) completionHandler(NSFileProviderError(.notAuthenticated)) return Progress() } @@ -280,6 +338,7 @@ import OSLog Logger.fileProviderExtension.error( "Not deleting item \(identifier.rawValue, privacy: .public), item not found" ) + insertErrorAction(actionId) completionHandler(NSFileProviderError(.noSuchItem)) return Progress() } @@ -288,7 +347,10 @@ import OSLog Task { let error = await item.delete() if error != nil { + insertErrorAction(actionId) signalEnumerator(completionHandler: { _ in }) + } else { + removeSyncAction(actionId) } progress.completedUnitCount = 1 completionHandler(await item.delete()) @@ -311,7 +373,8 @@ import OSLog ncAccount: ncAccount, remoteInterface: ncKit, domain: domain, - fastEnumeration: config.fastEnumerationEnabled + fastEnumeration: config.fastEnumerationEnabled, + listener: self ) } @@ -342,6 +405,8 @@ import OSLog materialisedEnumerator.enumerateItems(for: materialisedObserver, startingAt: startingPage) } + // MARK: - Helper functions + func signalEnumerator(completionHandler: @escaping (_ error: Error?) -> Void) { guard let fpManager = NSFileProviderManager(for: domain) else { Logger.fileProviderExtension.error( diff --git a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj index a150d71c4053d..7162acf23b440 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 5307A6E62965C6FA001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E52965C6FA001E0C6A /* NextcloudKit */; }; 5307A6E82965DAD8001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E72965DAD8001E0C6A /* NextcloudKit */; }; 531522822B8E01C6002E31BE /* ShareTableItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 531522812B8E01C6002E31BE /* ShareTableItemView.xib */; }; + 532572082C4690340068DEC3 /* FileProviderExtension+EnumerationListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532572072C4690340068DEC3 /* FileProviderExtension+EnumerationListener.swift */; }; 5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */; }; 5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */; }; 5358F2B92BAA0F5300E3C729 /* NextcloudCapabilitiesKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */; }; @@ -148,6 +149,7 @@ /* Begin PBXFileReference section */ 531522812B8E01C6002E31BE /* ShareTableItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShareTableItemView.xib; sourceTree = ""; }; + 532572072C4690340068DEC3 /* FileProviderExtension+EnumerationListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+EnumerationListener.swift"; sourceTree = ""; }; 5350E4E72B0C514400F276CB /* ClientCommunicationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ClientCommunicationProtocol.h; sourceTree = ""; }; 5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientCommunicationService.swift; sourceTree = ""; }; 5350E4EA2B0C9CE100F276CB /* FileProviderExt-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FileProviderExt-Bridging-Header.h"; sourceTree = ""; }; @@ -294,6 +296,7 @@ 53D666602B70C9A70042C03D /* FileProviderConfig.swift */, 538E396C27F4765000FA63D5 /* FileProviderExtension.swift */, 53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */, + 532572072C4690340068DEC3 /* FileProviderExtension+EnumerationListener.swift */, 5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */, 536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */, 538E397327F4765000FA63D5 /* FileProviderExt.entitlements */, @@ -671,6 +674,7 @@ 535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */, 537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */, 5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */, + 532572082C4690340068DEC3 /* FileProviderExtension+EnumerationListener.swift in Sources */, 5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 083f58a978b09..6fdfb07ff9b1b 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -461,7 +461,7 @@ Application::Application(int &argc, char **argv) AccountSetupCommandLineManager::destroy(); #if defined(BUILD_FILE_PROVIDER_MODULE) - _fileProvider.reset(new Mac::FileProvider); + Mac::FileProvider::instance(); #endif } diff --git a/src/gui/application.h b/src/gui/application.h index 879ae51efede4..9ec6e121bda39 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -49,12 +49,6 @@ class Folder; class ShellExtensionsServer; class SslErrorDialog; -#ifdef Q_OS_MACOS -namespace Mac { -class FileProvider; -} -#endif - /** * @brief The Application class * @ingroup gui @@ -163,8 +157,6 @@ protected slots: QScopedPointer _folderManager; #if defined(Q_OS_WIN) QScopedPointer _shellExtensionsServer; -#elif defined(Q_OS_MACOS) - QScopedPointer _fileProvider; #endif }; diff --git a/src/gui/macOS/fileprovider.h b/src/gui/macOS/fileprovider.h index d4ddcf596d30a..d3ebb62737316 100644 --- a/src/gui/macOS/fileprovider.h +++ b/src/gui/macOS/fileprovider.h @@ -41,6 +41,7 @@ class FileProvider : public QObject [[nodiscard]] FileProviderXPC *xpc() const; [[nodiscard]] FileProviderDomainManager *domainManager() const; + [[nodiscard]] FileProviderSocketServer *socketServer() const; private slots: void configureXPC(); diff --git a/src/gui/macOS/fileprovider_mac.mm b/src/gui/macOS/fileprovider_mac.mm index 73c2e0d259f95..194d1da6d129f 100644 --- a/src/gui/macOS/fileprovider_mac.mm +++ b/src/gui/macOS/fileprovider_mac.mm @@ -107,5 +107,10 @@ return _domainManager.get(); } +FileProviderSocketServer *FileProvider::socketServer() const +{ + return _socketServer.get(); +} + } // namespace Mac } // namespace OCC diff --git a/src/gui/macOS/fileprovidersocketcontroller.cpp b/src/gui/macOS/fileprovidersocketcontroller.cpp index 0b34a9962ad61..fcd5517b77de6 100644 --- a/src/gui/macOS/fileprovidersocketcontroller.cpp +++ b/src/gui/macOS/fileprovidersocketcontroller.cpp @@ -88,6 +88,10 @@ void FileProviderSocketController::parseReceivedLine(const QString &receivedLine if (command == QStringLiteral("FILE_PROVIDER_DOMAIN_IDENTIFIER_REQUEST_REPLY")) { _accountState = FileProviderDomainManager::accountStateFromFileProviderDomainIdentifier(argument); sendAccountDetails(); + reportSyncState("SYNC_PREPARING"); + return; + } else if (command == "FILE_PROVIDER_DOMAIN_SYNC_STATE_CHANGE") { + reportSyncState(argument); return; } @@ -168,6 +172,11 @@ void FileProviderSocketController::slotAccountStateChanged(const AccountState::S } } +AccountStatePtr FileProviderSocketController::accountState() const +{ + return _accountState; +} + void FileProviderSocketController::sendNotAuthenticated() const { Q_ASSERT(_accountState); @@ -216,6 +225,30 @@ void FileProviderSocketController::sendAccountDetails() const sendMessage(message); } -} +void FileProviderSocketController::reportSyncState(const QString &receivedState) +{ + if (!accountState()) { + qCWarning(lcFileProviderSocketController) << "No account state available to report sync state"; + return; + } + auto syncState = SyncResult::Status::Undefined; + if (receivedState == "SYNC_PREPARING") { + syncState = SyncResult::Status::SyncPrepare; + } else if (receivedState == "SYNC_STARTED") { + syncState = SyncResult::Status::SyncRunning; + } else if (receivedState == "SYNC_FINISHED") { + syncState = SyncResult::Status::Success; + } else if (receivedState == "SYNC_FAILED") { + syncState = SyncResult::Status::Problem; + } else if (receivedState == "SYNC_PAUSED") { + syncState = SyncResult::Status::Paused; + } else { + qCWarning(lcFileProviderSocketController) << "Unknown sync state received:" << receivedState; + } + emit syncStateChanged(_accountState->account(), syncState); } + +} // namespace Mac + +} // namespace OCC diff --git a/src/gui/macOS/fileprovidersocketcontroller.h b/src/gui/macOS/fileprovidersocketcontroller.h index ca7c353343237..30fc8251a8285 100644 --- a/src/gui/macOS/fileprovidersocketcontroller.h +++ b/src/gui/macOS/fileprovidersocketcontroller.h @@ -16,7 +16,8 @@ #include -#include "accountstate.h" +#include "gui/accountstate.h" +#include "libsync/syncresult.h" class QLocalSocket; @@ -31,8 +32,11 @@ class FileProviderSocketController : public QObject public: explicit FileProviderSocketController(QLocalSocket * const socket, QObject * const parent = nullptr); + [[nodiscard]] AccountStatePtr accountState() const; + signals: void socketDestroyed(const QLocalSocket * const socket); + void syncStateChanged(const AccountPtr &account, SyncResult::Status state) const; public slots: void sendMessage(const QString &message) const; @@ -50,6 +54,8 @@ private slots: void sendAccountDetails() const; void sendNotAuthenticated() const; + void reportSyncState(const QString &receivedState); + private: QPointer _socket; AccountStatePtr _accountState; diff --git a/src/gui/macOS/fileprovidersocketserver.cpp b/src/gui/macOS/fileprovidersocketserver.cpp index 70b01615705fd..aeebb23ff053b 100644 --- a/src/gui/macOS/fileprovidersocketserver.cpp +++ b/src/gui/macOS/fileprovidersocketserver.cpp @@ -17,6 +17,8 @@ #include #include +#include "libsync/account.h" + #include "fileprovidersocketcontroller.h" namespace OCC { @@ -62,6 +64,8 @@ void FileProviderSocketServer::slotNewConnection() } const FileProviderSocketControllerPtr socketController(new FileProviderSocketController(socket, this)); + connect(socketController.data(), &FileProviderSocketController::syncStateChanged, + this, &FileProviderSocketServer::slotSyncStateChanged); connect(socketController.data(), &FileProviderSocketController::socketDestroyed, this, &FileProviderSocketServer::slotSocketDestroyed); _socketControllers.insert(socket, socketController); @@ -79,6 +83,21 @@ void FileProviderSocketServer::slotSocketDestroyed(const QLocalSocket * const so } } +void FileProviderSocketServer::slotSyncStateChanged(const AccountPtr &account, SyncResult::Status state) +{ + Q_ASSERT(account); + const auto userId = account->userIdAtHostWithPort(); + qCDebug(lcFileProviderSocketServer) << "Received sync state change for account" << userId << "state" << state; + _latestReceivedSyncStatus.insert(userId, state); + Q_EMIT syncStateChanged(account, state); +} + +SyncResult::Status FileProviderSocketServer::latestReceivedSyncStatusForAccount(const AccountPtr &account) const +{ + Q_ASSERT(account); + return _latestReceivedSyncStatus.value(account->userIdAtHostWithPort(), SyncResult::Undefined); +} + } // namespace Mac } // namespace OCC diff --git a/src/gui/macOS/fileprovidersocketserver.h b/src/gui/macOS/fileprovidersocketserver.h index e650e0ca2b4ac..d1c14b7fa08f4 100644 --- a/src/gui/macOS/fileprovidersocketserver.h +++ b/src/gui/macOS/fileprovidersocketserver.h @@ -17,6 +17,9 @@ #include #include +#include "libsync/accountfwd.h" +#include "libsync/syncresult.h" + namespace OCC { namespace Mac { @@ -46,15 +49,22 @@ class FileProviderSocketServer : public QObject public: explicit FileProviderSocketServer(QObject *parent = nullptr); + [[nodiscard]] SyncResult::Status latestReceivedSyncStatusForAccount(const AccountPtr &account) const; + +signals: + void syncStateChanged(const AccountPtr &account, SyncResult::Status state) const; + private slots: void startListening(); void slotNewConnection(); void slotSocketDestroyed(const QLocalSocket * const socket); + void slotSyncStateChanged(const AccountPtr &account, SyncResult::Status state); private: QString _socketPath; QLocalServer _socketServer; QHash _socketControllers; + QHash _latestReceivedSyncStatus; }; } // namespace Mac diff --git a/src/gui/macOS/fileproviderxpc.h b/src/gui/macOS/fileproviderxpc.h index 58c6a0211b309..4ca88c03bd5be 100644 --- a/src/gui/macOS/fileproviderxpc.h +++ b/src/gui/macOS/fileproviderxpc.h @@ -34,6 +34,8 @@ class FileProviderXPC : public QObject public: explicit FileProviderXPC(QObject *parent = nullptr); + [[nodiscard]] bool fileProviderExtReachable(const QString &extensionAccountId) const; + // Returns enabled and set state of fast enumeration for the given extension [[nodiscard]] std::optional> fastEnumerationStateForExtension(const QString &extensionAccountId) const; diff --git a/src/gui/macOS/fileproviderxpc_mac.mm b/src/gui/macOS/fileproviderxpc_mac.mm index cc0b7b4679230..e94dc2cbcca48 100644 --- a/src/gui/macOS/fileproviderxpc_mac.mm +++ b/src/gui/macOS/fileproviderxpc_mac.mm @@ -141,6 +141,22 @@ } } +bool FileProviderXPC::fileProviderExtReachable(const QString &extensionAccountId) const +{ + const auto service = (NSObject *)_clientCommServices.value(extensionAccountId); + if (service == nil) { + return false; + } + __block auto response = false; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [service getExtensionAccountIdWithCompletionHandler:^(NSString *const, NSError *const) { + response = true; + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, semaphoreWaitDelta)); + return response; +} + std::optional> FileProviderXPC::fastEnumerationStateForExtension(const QString &extensionAccountId) const { qCInfo(lcFileProviderXPC) << "Checking if fast enumeration is enabled for extension" << extensionAccountId; diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index f55b60f14fc64..9cc24ef7ae617 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -64,6 +64,8 @@ #include #ifdef BUILD_FILE_PROVIDER_MODULE +#include "macOS/fileprovider.h" +#include "macOS/fileproviderdomainmanager.h" #include "macOS/fileprovidersettingscontroller.h" #endif @@ -110,8 +112,11 @@ ownCloudGui::ownCloudGui(Application *parent) &ownCloudGui::slotUpdateProgress); FolderMan *folderMan = FolderMan::instance(); - connect(folderMan, &FolderMan::folderSyncStateChange, - this, &ownCloudGui::slotSyncStateChange); + connect(folderMan, &FolderMan::folderSyncStateChange, this, &ownCloudGui::slotSyncStateChange); + +#ifdef BUILD_FILE_PROVIDER_MODULE + connect(Mac::FileProvider::instance()->socketServer(), &Mac::FileProviderSocketServer::syncStateChanged, this, &ownCloudGui::slotComputeOverallSyncStatus); +#endif connect(Logger::instance(), &Logger::guiLog, this, &ownCloudGui::slotShowTrayMessage); connect(Logger::instance(), &Logger::guiMessage, this, &ownCloudGui::slotShowGuiMessage); @@ -279,40 +284,65 @@ void ownCloudGui::slotComputeOverallSyncStatus() { bool allSignedOut = true; bool allPaused = true; - bool allDisconnected = true; QVector problemAccounts; - auto setStatusText = [&](const QString &text) { - // FIXME: So this doesn't do anything? Needs to be revisited - Q_UNUSED(text) - // Don't overwrite the status if we're currently syncing - if (FolderMan::instance()->isAnySyncRunning()) - return; - //_actionStatus->setText(text); - }; - - foreach (auto a, AccountManager::instance()->accounts()) { - if (!a->isSignedOut()) { + + for (const auto &account : AccountManager::instance()->accounts()) { + if (!account->isSignedOut()) { allSignedOut = false; } - if (!a->isConnected()) { - problemAccounts.append(a); - } else { - allDisconnected = false; + if (!account->isConnected()) { + problemAccounts.append(account); } } - foreach (Folder *f, FolderMan::instance()->map()) { - if (!f->syncPaused()) { + for (const auto folder : FolderMan::instance()->map()) { + if (!folder->syncPaused()) { allPaused = false; } } +#ifdef BUILD_FILE_PROVIDER_MODULE + QList problemFileProviderAccounts; + QList syncingFileProviderAccounts; + QList successFileProviderAccounts; + + if (Mac::FileProvider::fileProviderAvailable()) { + for (const auto &accountState : AccountManager::instance()->accounts()) { + const auto accountFpId = Mac::FileProviderDomainManager::fileProviderDomainIdentifierFromAccountState(accountState); + if (!Mac::FileProviderSettingsController::instance()->vfsEnabledForAccount(accountFpId)) { + continue; + } + const auto fileProvider = Mac::FileProvider::instance(); + + if (!fileProvider->xpc()->fileProviderExtReachable(accountFpId)) { + problemFileProviderAccounts.append(accountFpId); + } else { + switch (const auto latestStatus = fileProvider->socketServer()->latestReceivedSyncStatusForAccount(accountState->account())) { + case SyncResult::Undefined: + case SyncResult::NotYetStarted: + break; + case SyncResult::SyncPrepare: + case SyncResult::SyncRunning: + case SyncResult::SyncAbortRequested: + syncingFileProviderAccounts.append(accountFpId); + break; + case SyncResult::Success: + successFileProviderAccounts.append(accountFpId); + break; + case SyncResult::Problem: + case SyncResult::Error: + case SyncResult::SetupError: + problemFileProviderAccounts.append(accountFpId); + break; + case SyncResult::Paused: + break; + } + } + } + } +#endif + if (!problemAccounts.empty()) { _tray->setIcon(Theme::instance()->folderOfflineIcon(true)); - if (allDisconnected) { - setStatusText(tr("Disconnected")); - } else { - setStatusText(tr("Disconnected from some accounts")); - } #ifdef Q_OS_WIN // Windows has a 128-char tray tooltip length limit. QStringList accountNames; @@ -323,11 +353,11 @@ void ownCloudGui::slotComputeOverallSyncStatus() #else QStringList messages; messages.append(tr("Disconnected from accounts:")); - foreach (AccountStatePtr a, problemAccounts) { - QString message = tr("Account %1: %2").arg(a->account()->displayName(), a->stateString(a->state())); - if (!a->connectionErrors().empty()) { + for (const auto &accountState : problemAccounts) { + QString message = tr("Account %1: %2").arg(accountState->account()->displayName(), accountState->stateString(accountState->state())); + if (!accountState->connectionErrors().empty()) { message += QLatin1String("\n"); - message += a->connectionErrors().join(QLatin1String("\n")); + message += accountState->connectionErrors().join(QLatin1String("\n")); } messages.append(message); } @@ -339,12 +369,10 @@ void ownCloudGui::slotComputeOverallSyncStatus() if (allSignedOut) { _tray->setIcon(Theme::instance()->folderOfflineIcon(true)); _tray->setToolTip(tr("Please sign in")); - setStatusText(tr("Signed out")); return; } else if (allPaused) { _tray->setIcon(Theme::instance()->syncStateIcon(SyncResult::Paused, true)); _tray->setToolTip(tr("Account synchronization is disabled")); - setStatusText(tr("Synchronization is paused")); return; } @@ -357,6 +385,18 @@ void ownCloudGui::slotComputeOverallSyncStatus() bool hasUnresolvedConflicts = false; FolderMan::trayOverallStatus(map.values(), &overallStatus, &hasUnresolvedConflicts); +#ifdef BUILD_FILE_PROVIDER_MODULE + if (!problemFileProviderAccounts.isEmpty()) { + overallStatus = SyncResult::Problem; + } else if (!syncingFileProviderAccounts.isEmpty() && + overallStatus != SyncResult::SyncRunning && + overallStatus != SyncResult::Problem && + overallStatus != SyncResult::Error && + overallStatus != SyncResult::SetupError) { + overallStatus = SyncResult::SyncRunning; + } +#endif + // If the sync succeeded but there are unresolved conflicts, // show the problem icon! auto iconStatus = overallStatus; @@ -373,37 +413,40 @@ void ownCloudGui::slotComputeOverallSyncStatus() _tray->setIcon(statusIcon); // create the tray blob message, check if we have an defined state +#ifdef BUILD_FILE_PROVIDER_MODULE + if (!map.isEmpty() || !syncingFileProviderAccounts.isEmpty() || !successFileProviderAccounts.isEmpty() || !problemFileProviderAccounts.isEmpty()) { +#else if (map.count() > 0) { +#endif #ifdef Q_OS_WIN // Windows has a 128-char tray tooltip length limit. trayMessage = folderMan->trayTooltipStatusString(overallStatus, hasUnresolvedConflicts, false); #else QStringList allStatusStrings; - foreach (Folder *folder, map.values()) { + const auto folders = map.values(); + for (const auto folder : folders) { QString folderMessage = FolderMan::trayTooltipStatusString( folder->syncResult().status(), folder->syncResult().hasUnresolvedConflicts(), folder->syncPaused()); allStatusStrings += tr("Folder %1: %2").arg(folder->shortGuiLocalPath(), folderMessage); } +#ifdef BUILD_FILE_PROVIDER_MODULE + for (const auto &accountId : syncingFileProviderAccounts) { + allStatusStrings += tr("macOS VFS for %1: Sync is running.").arg(accountId); + } + for (const auto &accountId : successFileProviderAccounts) { + allStatusStrings += tr("macOS VFS for %1: Last sync was successful.").arg(accountId); + } + for (const auto &accountId : problemFileProviderAccounts) { + allStatusStrings += tr("macOS VFS for %1: A problem was encountered.").arg(accountId); + } +#endif trayMessage = allStatusStrings.join(QLatin1String("\n")); #endif _tray->setToolTip(trayMessage); - - if (overallStatus == SyncResult::Success || overallStatus == SyncResult::Problem) { - if (hasUnresolvedConflicts) { - setStatusText(tr("Unresolved conflicts")); - } else { - setStatusText(tr("Up to date")); - } - } else if (overallStatus == SyncResult::Paused) { - setStatusText(tr("Synchronization is paused")); - } else { - setStatusText(tr("Error during synchronization")); - } } else { _tray->setToolTip(tr("There are no sync folders configured.")); - setStatusText(tr("No sync folders configured")); } }