diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift index 9f042e6d27393..6fb8c5d3ce8c1 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift @@ -58,6 +58,40 @@ extension FileProviderExtension: NSFileProviderCustomAction { onItemsWithIdentifiers: itemIdentifiers, completionHandler: completionHandler ) + case "com.nextcloud.desktopclient.FileProviderExt.OpenInBrowserAction": + guard let itemIdentifier = itemIdentifiers.first else { + logger.error("Failed to get first item identifier for open in browser action.") + completionHandler(NSFileProviderError(.noSuchItem)) + return Progress() + } + + guard let dbManager else { + logger.error("Cannot open in browser due to database manager not being available.", [.item: itemIdentifier]) + completionHandler(NSFileProviderError(.cannotSynchronize)) + return Progress() + } + + Task { + guard let userVisibleURL = try await manager?.getUserVisibleURL(for: itemIdentifier) else { + logger.error("Failed to get user-visible URL for item.", [.item: itemIdentifier]) + completionHandler(NSFileProviderError(.noSuchItem)) + return + } + + guard let metadata = dbManager.itemMetadata(itemIdentifier) else { + logger.error("Failed to get metadata for item.", [.item: itemIdentifier]) + completionHandler(NSFileProviderError(.cannotSynchronize)) + return + } + + let path = userVisibleURL.path + let domainIdentifier = domain.identifier.rawValue + logger.info("Telling main app to open item in browser.", [.item: path, .domain: domainIdentifier]) + app?.openInBrowserForPath(path, remoteItemPath: metadata.path, withDomainIdentifier: domainIdentifier) + completionHandler(nil) + } + + return Progress() default: logger.error("Unsupported action: \(actionIdentifier.rawValue)") completionHandler(NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError)) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist index ceb30173c3211..c5db5c047580d 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Info.plist @@ -45,6 +45,14 @@ NSExtensionFileProviderActionName File actions + + NSExtensionFileProviderActionActivationRule + SUBQUERY ( fileproviderItems, $fileproviderItem, !($fileproviderItem.contentType.identifier UTI-CONFORMS-TO "public.folder") ).@count > 0 + NSExtensionFileProviderActionIdentifier + com.nextcloud.desktopclient.FileProviderExt.OpenInBrowserAction + NSExtensionFileProviderActionName + Open in browser + NSExtensionFileProviderDocumentGroup $(DEVELOPMENT_TEAM).$(OC_APPLICATION_REV_DOMAIN) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/AppProtocol.h b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/AppProtocol.h index 9e8f599b12be8..bc7f68cf126e2 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/AppProtocol.h +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/AppProtocol.h @@ -23,6 +23,18 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)presentFileActions:(NSString *)fileId path:(NSString *)path remoteItemPath:(NSString *)remoteItemPath withDomainIdentifier:(NSString *)domainIdentifier; +/** + * @brief The file provider extension can request opening an item in the browser. + * + * The main app decides whether to use direct editing (`EDIT`) or private link (`OPEN_PRIVATE_LINK`) + * depending on the direct editor availability for the given local file. + * + * @param path The local and absolute path for the item to open. + * @param remoteItemPath The server-side path of the item (reserved for future fallback logic). + * @param domainIdentifier The file provider domain identifier for the account that manages this file. + */ +- (void)openInBrowserForPath:(NSString *)path remoteItemPath:(NSString *)remoteItemPath withDomainIdentifier:(NSString *)domainIdentifier; + /** * @brief The file provider extension can report its synchronization status as a string constant value to the main app through this method. * @param status The synchronization status string. @@ -34,4 +46,3 @@ NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END #endif /* AppProtocol_h */ - diff --git a/src/gui/macOS/fileproviderservice.mm b/src/gui/macOS/fileproviderservice.mm index 0b2c97e9171cd..17bdf87640719 100644 --- a/src/gui/macOS/fileproviderservice.mm +++ b/src/gui/macOS/fileproviderservice.mm @@ -14,6 +14,8 @@ #include #include "accountmanager.h" +#include "folderman.h" +#include "socketapi/socketapi.h" namespace OCC { @@ -107,6 +109,37 @@ - (void)reportSyncStatus:(NSString *)status forDomainIdentifier:(NSString *)doma Q_ARG(OCC::SyncResult::Status, syncState)); } +- (void)openInBrowserForPath:(NSString *)path remoteItemPath:(NSString *)remoteItemPath withDomainIdentifier:(NSString *)domainIdentifier +{ + Q_UNUSED(remoteItemPath) + Q_UNUSED(domainIdentifier) + + const auto localPath = QString::fromNSString(path); + + qCDebug(OCC::lcMacFileProviderService) << "Received open in browser request for path:" << localPath; + + if (!_service) { + qCWarning(OCC::lcMacFileProviderService) << "No service available to open item in browser"; + return; + } + + auto *const socketApi = OCC::FolderMan::instance()->socketApi(); + if (!socketApi) { + qCWarning(OCC::lcMacFileProviderService) << "SocketApi is unavailable, cannot open item in browser:" << localPath; + return; + } + + // Execute on the socket API object thread. + QMetaObject::invokeMethod(socketApi, [socketApi, localPath]() { + OCC::SocketListener *nullListener = nullptr; + if (socketApi->getDirectEditorForLocalFile(localPath)) { + socketApi->command_EDIT(localPath, nullListener); + } else { + socketApi->command_OPEN_PRIVATE_LINK(localPath, nullListener); + } + }, Qt::QueuedConnection); +} + @end namespace OCC { @@ -166,4 +199,3 @@ - (void)reportSyncStatus:(NSString *)status forDomainIdentifier:(NSString *)doma } // namespace Mac } // namespace OCC -