Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@
<key>NSExtensionFileProviderActionName</key>
<string>File actions</string>
</dict>
<dict>
<key>NSExtensionFileProviderActionActivationRule</key>
<string>SUBQUERY ( fileproviderItems, $fileproviderItem, !($fileproviderItem.contentType.identifier UTI-CONFORMS-TO "public.folder") ).@count &gt; 0</string>
<key>NSExtensionFileProviderActionIdentifier</key>
<string>com.nextcloud.desktopclient.FileProviderExt.OpenInBrowserAction</string>
<key>NSExtensionFileProviderActionName</key>
<string>Open in browser</string>
</dict>
</array>
<key>NSExtensionFileProviderDocumentGroup</key>
<string>$(DEVELOPMENT_TEAM).$(OC_APPLICATION_REV_DOMAIN)</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#ifndef AppProtocol_h
#define AppProtocol_h

#import <Foundation/Foundation.h>

Check failure on line 9 in shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/AppProtocol.h

View workflow job for this annotation

GitHub Actions / build

shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/AppProtocol.h:9:9 [clang-diagnostic-error]

'Foundation/Foundation.h' file not found
NS_ASSUME_NONNULL_BEGIN

/**
Expand All @@ -23,6 +23,18 @@
*/
- (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.
Expand All @@ -34,4 +46,3 @@

NS_ASSUME_NONNULL_END
#endif /* AppProtocol_h */

34 changes: 33 additions & 1 deletion src/gui/macOS/fileproviderservice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <QMetaObject>

#include "accountmanager.h"
#include "folderman.h"
#include "socketapi/socketapi.h"

namespace OCC {

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -166,4 +199,3 @@ - (void)reportSyncStatus:(NSString *)status forDomainIdentifier:(NSString *)doma
} // namespace Mac

} // namespace OCC

Loading