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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ The following details are important when working on the desktop client on macOS.
- The PIMPL pattern is an established convention in the Objective-C++ source code files under `src/gui/macOS`.
- To abstract macOS and Objective-C specific APIs, prefer to use Qt and C++ types in public identifiers declared in headers. Use and prefer Objective-C or native macOS features only internally in implementations. This rule applies only to the code in `src/gui/macOS`, though.
- When writing code in Swift, respect strict concurrency rules and Swift 6 compatibility.
- Manage memory explicitly and manually when writing or updating code located under `./src`. For example, do not use features like `__weak` from automatic reference counting in Objective-C because ARC is not used in this project.

### Tests

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ extension FileProviderExtension: NSFileProviderCustomAction {
}
}

for try await result in group {
for try await _ in group {
progress.completedUnitCount = 1
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: progress.completedUnitCount is being set to 1 instead of incremented. This should be progress.completedUnitCount += 1 or progress.completedUnitCount++ to properly track progress. Currently, it will always show as 1 out of N items completed, never reaching 100%.

Suggested change
progress.completedUnitCount = 1
progress.completedUnitCount += 1

Copilot uses AI. Check for mistakes.
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ extension FileProviderExtension: NSXPCListenerDelegate {
if let appService = remoteObjectProxy as? AppProtocol {
logger.info("Succeeded to cast remote object proxy, adopting it!")
self.app = appService

// Initial status report as soon as the app service is available.
updatedSyncStateReporting(oldActions: Set<UUID>())
} else {
logger.error("Failed to cast remote object proxy to AppProtocol!")
self.app = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later

import FileProvider
import NCDesktopClientSocketKit
import NextcloudKit
import NextcloudFileProviderKit
import OSLog
Expand Down Expand Up @@ -46,18 +45,6 @@ import OSLog
var ignoredFiles: IgnoredFilesMatcher?
lazy var ncKitBackground = NKBackground(nkCommonInstance: ncKit.nkCommonInstance)

lazy var socketClient: LocalSocketClient? = {
guard let containerUrl = FileManager.default.applicationGroupContainer() else {
logger.fault("Won't start socket client, no container URL available!")
return nil;
}

let socketPath = containerUrl.appendingPathComponent("fps", conformingTo: .archive)
let lineProcessor = FileProviderSocketLineProcessor(delegate: self, log: log)

return LocalSocketClient(socketPath: socketPath.path, lineProcessor: lineProcessor)
}()

var syncActions = Set<UUID>()
var errorActions = Set<UUID>()
var actionsLock = NSLock()
Expand Down Expand Up @@ -101,7 +88,6 @@ import OSLog
self.keychain = Keychain(log: log)

super.init()
socketClient?.start()
}

func invalidate() {
Expand Down Expand Up @@ -548,13 +534,6 @@ import OSLog
fpManager.signalEnumerator(for: .workingSet, completionHandler: completionHandler)
}

@objc func sendFileProviderDomainIdentifier() {
let command = "FILE_PROVIDER_DOMAIN_IDENTIFIER_REQUEST_REPLY"
let argument = domain.identifier.rawValue
let message = command + ":" + argument + "\n"
socketClient?.sendMessage(message)
}

private func signalEnumeratorAfterAccountSetup() {
guard let fpManager = NSFileProviderManager(for: domain) else {
logger.error("Could not get file provider manager for domain \(self.domain.displayName), cannot notify after account setup")
Expand Down

This file was deleted.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this duplicate of FinderSyncExt/Services/FinderSyncAppProtocol.h on purpose? if yes: could a symlink to it work here as well?

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#ifndef FinderSyncAppProtocol_h
#define FinderSyncAppProtocol_h

#import <Foundation/Foundation.h>

Check failure on line 9 in shell_integration/MacOSX/NextcloudIntegration/FinderSyncAppProtocol.h

View workflow job for this annotation

GitHub Actions / build

shell_integration/MacOSX/NextcloudIntegration/FinderSyncAppProtocol.h:9:9 [clang-diagnostic-error]

'Foundation/Foundation.h' file not found

/**
* @brief The main app APIs exposed through XPC.
*
* This protocol is implemented by the main Nextcloud app and allows the FinderSync
* extension to query file statuses, menu items, and execute commands via XPC.
*/
@protocol FinderSyncAppProtocol

/**
* @brief Retrieve the sync status for a file.
* @param path The absolute path to the file.
* @param completionHandler Callback with status string (e.g., "SYNC", "OK", "ERROR") or error.
*/
- (void)retrieveFileStatusForPath:(NSString *)path
completionHandler:(void(^)(NSString *status, NSError *error))completionHandler;

/**
* @brief Retrieve the sync status for a folder.
* @param path The absolute path to the folder.
* @param completionHandler Callback with status string (e.g., "SYNC", "OK", "ERROR") or error.
*/
- (void)retrieveFolderStatusForPath:(NSString *)path
completionHandler:(void(^)(NSString *status, NSError *error))completionHandler;

/**
* @brief Get localized strings for the FinderSync extension UI.
* @param completionHandler Callback with dictionary of string keys and localized values, or error.
*/
- (void)getLocalizedStringsWithCompletionHandler:(void(^)(NSDictionary<NSString *, NSString *> *strings, NSError *error))completionHandler;

/**
* @brief Get context menu items for the given paths.
* @param paths Array of absolute paths for which to get menu items.
* @param completionHandler Callback with array of menu item dictionaries (command, flags, text) or error.
*/
- (void)getMenuItemsForPaths:(NSArray<NSString *> *)paths
completionHandler:(void(^)(NSArray<NSDictionary *> *menuItems, NSError *error))completionHandler;

/**
* @brief Execute a menu command for the given paths.
* @param command The command identifier (e.g., "SHARE", "ACTIVITY").
* @param paths Array of absolute paths on which to execute the command.
* @param completionHandler Callback with error if execution failed, or nil on success.
*/
- (void)executeMenuCommand:(NSString *)command
forPaths:(NSArray<NSString *> *)paths
completionHandler:(void(^)(NSError *error))completionHandler;

@end

#endif /* FinderSyncAppProtocol_h */
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#import <Cocoa/Cocoa.h>

Check failure on line 7 in shell_integration/MacOSX/NextcloudIntegration/FinderSyncExt/FinderSync.h

View workflow job for this annotation

GitHub Actions / build

shell_integration/MacOSX/NextcloudIntegration/FinderSyncExt/FinderSync.h:7:9 [clang-diagnostic-error]

'Cocoa/Cocoa.h' file not found
#import <FinderSync/FinderSync.h>
#import <NCDesktopClientSocketKit/LocalSocketClient.h>

#import "SyncClient.h"
#import "FinderSyncSocketLineProcessor.h"

@class FinderSyncXPCManager;

@interface FinderSync : FIFinderSync <SyncClientDelegate>

@property FinderSyncSocketLineProcessor *lineProcessor;
@property LocalSocketClient *localSocketClient;
@property FinderSyncXPCManager *xpcManager;

@end
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

#import "FinderSync.h"
#import "FinderSyncXPCManager.h"

@interface FinderSync()
{
Expand All @@ -25,8 +26,6 @@ - (instancetype)init
if (self) {
FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
NSBundle *extBundle = [NSBundle bundleForClass:[self class]];
// This was added to the bundle's Info.plist to get it from the build system
NSString *socketApiPrefix = [extBundle objectForInfoDictionaryKey:@"SocketApiPrefix"];

NSImage *ok = [extBundle imageForResource:@"ok.icns"];
NSImage *ok_swm = [extBundle imageForResource:@"ok_swm.icns"];
Expand All @@ -45,23 +44,11 @@ - (instancetype)init
[syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE+SWM"];
[syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR+SWM"];

NSURL *container = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:socketApiPrefix];
NSURL *library = [container URLByAppendingPathComponent:@"Library" isDirectory:true];
NSURL *applicationSupport = [library URLByAppendingPathComponent:@"Application Support" isDirectory:true];
NSURL *socketPath = [applicationSupport URLByAppendingPathComponent:@"s" isDirectory:NO];

NSLog(@"Socket path: %@", socketPath.path);

if (socketPath.path) {
self.lineProcessor = [[FinderSyncSocketLineProcessor alloc] initWithDelegate:self];
self.localSocketClient = [[LocalSocketClient alloc] initWithSocketPath:socketPath.path
lineProcessor:self.lineProcessor];
[self.localSocketClient start];
[self.localSocketClient askOnSocket:@"" query:@"GET_STRINGS"];
} else {
NSLog(@"No socket path. Not initiating local socket client.");
self.localSocketClient = nil;
}
// Initialize XPC manager instead of socket client
NSLog(@"Initializing FinderSync XPC manager");
self.xpcManager = [[FinderSyncXPCManager alloc] initWithDelegate:self];
[self.xpcManager start];
[self.xpcManager askOnSocket:@"" query:@"GET_STRINGS"];

_registeredDirectories = NSMutableSet.set;
_strings = NSMutableDictionary.dictionary;
Expand All @@ -82,7 +69,7 @@ - (void)requestBadgeIdentifierForURL:(NSURL *)url
}

NSString* normalizedPath = [[url path] decomposedStringWithCanonicalMapping];
[self.localSocketClient askForIcon:normalizedPath isDirectory:isDir];
[self.xpcManager askForIcon:normalizedPath isDirectory:isDir];
}

#pragma mark - Menu and toolbar item support
Expand Down Expand Up @@ -110,10 +97,10 @@ - (void)waitForMenuToArrive

- (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu
{
if(![self.localSocketClient isConnected]) {
if(![self.xpcManager isConnected]) {
return nil;
}

FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
NSMutableSet *rootPaths = [[NSMutableSet alloc] init];
[syncController.directoryURLs enumerateObjectsUsingBlock: ^(id obj, BOOL *stop) {
Expand All @@ -133,9 +120,9 @@ - (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu
}];

NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
[self.localSocketClient askOnSocket:paths query:@"GET_MENU_ITEMS"];
// Since the LocalSocketClient communicates asynchronously. wait here until the menu
[self.xpcManager askOnSocket:paths query:@"GET_MENU_ITEMS"];

// Since the XPC communication is asynchronous, wait here until the menu
// is delivered by another thread
[self waitForMenuToArrive];

Expand Down Expand Up @@ -171,7 +158,7 @@ - (void)subMenuActionClicked:(id)sender {
long idx = [(NSMenuItem*)sender tag];
NSString *command = [[_menuItems objectAtIndex:idx] valueForKey:@"command"];
NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
[self.localSocketClient askOnSocket:paths query:command];
[self.xpcManager askOnSocket:paths query:command];
}

#pragma mark - SyncClientProxyDelegate implementation
Expand Down

This file was deleted.

Loading
Loading