/* * Copyright (C) by Jocelyn Turcotte * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #import "FinderSync.h" @implementation FinderSync - (instancetype)init { self = [super init]; 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"]; NSImage *sync = [extBundle imageForResource:@"sync.icns"]; NSImage *warning = [extBundle imageForResource:@"warning.icns"]; NSImage *error = [extBundle imageForResource:@"error.icns"]; [syncController setBadgeImage:ok label:@"Up to date" forBadgeIdentifier:@"OK"]; [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC"]; [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW"]; [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE"]; [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR"]; [syncController setBadgeImage:ok_swm label:@"Shared" forBadgeIdentifier:@"OK+SWM"]; [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC+SWM"]; [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW+SWM"]; [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE+SWM"]; [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR+SWM"]; // The Mach port name needs to: // - Be prefixed with the code signing Team ID // - Then infixed with the sandbox App Group // - The App Group itself must be a prefix of (or equal to) the application bundle identifier // We end up in the official signed client with: 9B5WD74GWJ.com.owncloud.desktopclient.socketApi // With ad-hoc signing (the '-' signing identity) we must drop the Team ID. // When the code isn't sandboxed (e.g. the OC client or the legacy overlay icon extension) // the OS doesn't seem to put any restriction on the port name, so we just follow what // the sandboxed App Extension needs. // https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24 NSString *serverName = [socketApiPrefix stringByAppendingString:@".socketApi"]; // NSLog(@"FinderSync serverName %@", serverName); _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName]; _registeredDirectories = [[NSMutableSet alloc] init]; _strings = [[NSMutableDictionary alloc] init]; [_syncClientProxy start]; return self; } #pragma mark - Primary Finder Sync protocol methods - (void)requestBadgeIdentifierForURL:(NSURL *)url { BOOL isDir; if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory: &isDir] == NO) { NSLog(@"ERROR: Could not determine file type of %@", [url path]); isDir = NO; } NSString* normalizedPath = [[url path] decomposedStringWithCanonicalMapping]; [_syncClientProxy askForIcon:normalizedPath isDirectory:isDir]; } #pragma mark - Menu and toolbar item support - (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu { FIFinderSyncController *syncController = [FIFinderSyncController defaultController]; NSMutableSet *rootPaths = [[NSMutableSet alloc] init]; [syncController.directoryURLs enumerateObjectsUsingBlock: ^(id obj, BOOL *stop) { [rootPaths addObject:[obj path]]; }]; // The server doesn't support sharing a root directory so do not show the option in this case. // It is still possible to get a problematic sharing by selecting both the root and a child, // but this is so complicated to do and meaningless that it's not worth putting this check // also in shareMenuAction. __block BOOL onlyRootsSelected = YES; [syncController.selectedItemURLs enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { if (![rootPaths member:[obj path]]) { onlyRootsSelected = NO; *stop = YES; } }]; id contextMenuTitle = [_strings objectForKey:@"CONTEXT_MENU_TITLE"]; id shareTitle = [_strings objectForKey:@"SHARE_MENU_TITLE"]; id copyLinkTitle = [_strings objectForKey:@"COPY_PRIVATE_LINK_MENU_TITLE"]; id emailLinkTitle = [_strings objectForKey:@"EMAIL_PRIVATE_LINK_MENU_TITLE"]; if (contextMenuTitle && !onlyRootsSelected) { NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; NSMenu *subMenu = [[NSMenu alloc] initWithTitle:@""]; NSMenuItem *subMenuItem = [menu addItemWithTitle:contextMenuTitle action:nil keyEquivalent:@""]; subMenuItem.submenu = subMenu; subMenuItem.image = [[NSBundle mainBundle] imageForResource:@"app.icns"]; [subMenu addItemWithTitle:shareTitle action:@selector(shareMenuAction:) keyEquivalent:@""]; [subMenu addItemWithTitle:copyLinkTitle action:@selector(copyLinkMenuAction:) keyEquivalent:@""]; [subMenu addItemWithTitle:emailLinkTitle action:@selector(emailLinkMenuAction:) keyEquivalent:@""]; return menu; } return nil; } - (IBAction)shareMenuAction:(id)sender { NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs]; [items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping]; [_syncClientProxy askOnSocket:normalizedPath query:@"SHARE"]; }]; } - (IBAction)copyLinkMenuAction:(id)sender { NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs]; [items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping]; [_syncClientProxy askOnSocket:normalizedPath query:@"COPY_PRIVATE_LINK"]; }]; } - (IBAction)emailLinkMenuAction:(id)sender { NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs]; [items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping]; [_syncClientProxy askOnSocket:normalizedPath query:@"EMAIL_PRIVATE_LINK"]; }]; } #pragma mark - SyncClientProxyDelegate implementation - (void)setResultForPath:(NSString*)path result:(NSString*)result { NSString *normalizedPath = [path decomposedStringWithCanonicalMapping]; [[FIFinderSyncController defaultController] setBadgeIdentifier:result forURL:[NSURL fileURLWithPath:normalizedPath]]; } - (void)reFetchFileNameCacheForPath:(NSString*)path { } - (void)registerPath:(NSString*)path { assert(_registeredDirectories); [_registeredDirectories addObject:[NSURL fileURLWithPath:path]]; [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories; } - (void)unregisterPath:(NSString*)path { [_registeredDirectories removeObject:[NSURL fileURLWithPath:path]]; [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories; } - (void)setString:(NSString*)key value:(NSString*)value { [_strings setObject:value forKey:key]; } - (void)connectionDidDie { [_strings removeAllObjects]; [_registeredDirectories removeAllObjects]; // For some reason the FIFinderSync cache doesn't seem to be cleared for the root item when // we reset the directoryURLs (seen on macOS 10.12 at least). // First setting it to the FS root and then setting it to nil seems to work around the issue. [FIFinderSyncController defaultController].directoryURLs = [NSSet setWithObject:[NSURL fileURLWithPath:@"/"]]; // This will tell Finder that this extension isn't attached to any directory // until we can reconnect to the sync client. [FIFinderSyncController defaultController].directoryURLs = nil; } @end