FinderSync.m 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. * Copyright (C) by Jocelyn Turcotte <jturcotte@woboq.com>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  11. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  12. * for more details.
  13. */
  14. #import "FinderSync.h"
  15. @implementation FinderSync
  16. - (instancetype)init
  17. {
  18. self = [super init];
  19. if (self) {
  20. FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
  21. NSBundle *extBundle = [NSBundle bundleForClass:[self class]];
  22. // This was added to the bundle's Info.plist to get it from the build system
  23. NSString *socketApiPrefix = [extBundle objectForInfoDictionaryKey:@"SocketApiPrefix"];
  24. NSImage *ok = [extBundle imageForResource:@"ok.icns"];
  25. NSImage *ok_swm = [extBundle imageForResource:@"ok_swm.icns"];
  26. NSImage *sync = [extBundle imageForResource:@"sync.icns"];
  27. NSImage *warning = [extBundle imageForResource:@"warning.icns"];
  28. NSImage *error = [extBundle imageForResource:@"error.icns"];
  29. [syncController setBadgeImage:ok label:@"Up to date" forBadgeIdentifier:@"OK"];
  30. [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC"];
  31. [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW"];
  32. [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE"];
  33. [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR"];
  34. [syncController setBadgeImage:ok_swm label:@"Shared" forBadgeIdentifier:@"OK+SWM"];
  35. [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC+SWM"];
  36. [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW+SWM"];
  37. [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE+SWM"];
  38. [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR+SWM"];
  39. // The Mach port name needs to:
  40. // - Be prefixed with the code signing Team ID
  41. // - Then infixed with the sandbox App Group
  42. // - The App Group itself must be a prefix of (or equal to) the application bundle identifier
  43. // We end up in the official signed client with: 9B5WD74GWJ.com.owncloud.desktopclient.socket
  44. // With ad-hoc signing (the '-' signing identity) we must drop the Team ID.
  45. // When the code isn't sandboxed (e.g. the OC client or the legacy overlay icon extension)
  46. // the OS doesn't seem to put any restriction on the port name, so we just follow what
  47. // the sandboxed App Extension needs.
  48. // https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24
  49. NSURL *container = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:socketApiPrefix];
  50. NSURL *socketPath = [container URLByAppendingPathComponent:@".socket" isDirectory:NO];
  51. NSLog(@"Socket path: %@", socketPath.path);
  52. if (socketPath.path) {
  53. self.lineProcessor = [[FinderSyncSocketLineProcessor alloc] initWithDelegate:self];
  54. self.localSocketClient = [[LocalSocketClient alloc] initWithSocketPath:socketPath.path
  55. lineProcessor:self.lineProcessor];
  56. [self.localSocketClient start];
  57. [self.localSocketClient askOnSocket:@"" query:@"GET_STRINGS"];
  58. } else {
  59. NSLog(@"No socket path. Not initiating local socket client.");
  60. self.localSocketClient = nil;
  61. }
  62. }
  63. return self;
  64. }
  65. #pragma mark - Primary Finder Sync protocol methods
  66. - (void)requestBadgeIdentifierForURL:(NSURL *)url
  67. {
  68. BOOL isDir;
  69. if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory: &isDir] == NO) {
  70. NSLog(@"ERROR: Could not determine file type of %@", [url path]);
  71. isDir = NO;
  72. }
  73. NSString* normalizedPath = [[url path] decomposedStringWithCanonicalMapping];
  74. [self.localSocketClient askForIcon:normalizedPath isDirectory:isDir];
  75. }
  76. #pragma mark - Menu and toolbar item support
  77. - (NSString*) selectedPathsSeparatedByRecordSeparator
  78. {
  79. FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
  80. NSMutableString *string = [[NSMutableString alloc] init];
  81. [syncController.selectedItemURLs enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
  82. if (string.length > 0) {
  83. [string appendString:@"\x1e"]; // record separator
  84. }
  85. NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping];
  86. [string appendString:normalizedPath];
  87. }];
  88. return string;
  89. }
  90. - (void)waitForMenuToArrive
  91. {
  92. [self->_menuIsComplete lock];
  93. [self->_menuIsComplete wait];
  94. [self->_menuIsComplete unlock];
  95. }
  96. - (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu
  97. {
  98. if(![self.localSocketClient isConnected]) {
  99. return nil;
  100. }
  101. FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
  102. NSMutableSet *rootPaths = [[NSMutableSet alloc] init];
  103. [syncController.directoryURLs enumerateObjectsUsingBlock: ^(id obj, BOOL *stop) {
  104. [rootPaths addObject:[obj path]];
  105. }];
  106. // The server doesn't support sharing a root directory so do not show the option in this case.
  107. // It is still possible to get a problematic sharing by selecting both the root and a child,
  108. // but this is so complicated to do and meaningless that it's not worth putting this check
  109. // also in shareMenuAction.
  110. __block BOOL onlyRootsSelected = YES;
  111. [syncController.selectedItemURLs enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
  112. if (![rootPaths member:[obj path]]) {
  113. onlyRootsSelected = NO;
  114. *stop = YES;
  115. }
  116. }];
  117. NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
  118. [self.localSocketClient askOnSocket:paths query:@"GET_MENU_ITEMS"];
  119. // Since the LocalSocketClient communicates asynchronously. wait here until the menu
  120. // is delivered by another thread
  121. [self waitForMenuToArrive];
  122. id contextMenuTitle = [_strings objectForKey:@"CONTEXT_MENU_TITLE"];
  123. if (contextMenuTitle && !onlyRootsSelected) {
  124. NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
  125. NSMenu *subMenu = [[NSMenu alloc] initWithTitle:@""];
  126. NSMenuItem *subMenuItem = [menu addItemWithTitle:contextMenuTitle action:nil keyEquivalent:@""];
  127. subMenuItem.submenu = subMenu;
  128. subMenuItem.image = [[NSBundle mainBundle] imageForResource:@"app.icns"];
  129. // There is an annoying bug in macOS (at least 10.13.3), it does not use/copy over the representedObject of a menu item
  130. // So we have to use tag instead.
  131. int idx = 0;
  132. for (NSArray* item in _menuItems) {
  133. NSMenuItem *actionItem = [subMenu addItemWithTitle:[item valueForKey:@"text"]
  134. action:@selector(subMenuActionClicked:)
  135. keyEquivalent:@""];
  136. [actionItem setTag:idx];
  137. [actionItem setTarget:self];
  138. NSString *flags = [item valueForKey:@"flags"]; // e.g. "d"
  139. if ([flags rangeOfString:@"d"].location != NSNotFound) {
  140. [actionItem setEnabled:false];
  141. }
  142. idx++;
  143. }
  144. return menu;
  145. }
  146. return nil;
  147. }
  148. - (void)subMenuActionClicked:(id)sender {
  149. long idx = [(NSMenuItem*)sender tag];
  150. NSString *command = [[_menuItems objectAtIndex:idx] valueForKey:@"command"];
  151. NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
  152. [self.localSocketClient askOnSocket:paths query:command];
  153. }
  154. #pragma mark - SyncClientProxyDelegate implementation
  155. - (void)setResultForPath:(NSString*)path result:(NSString*)result
  156. {
  157. NSString *normalizedPath = [path decomposedStringWithCanonicalMapping];
  158. [[FIFinderSyncController defaultController] setBadgeIdentifier:result forURL:[NSURL fileURLWithPath:normalizedPath]];
  159. }
  160. - (void)reFetchFileNameCacheForPath:(NSString*)path
  161. {
  162. }
  163. - (void)registerPath:(NSString*)path
  164. {
  165. assert(_registeredDirectories);
  166. [_registeredDirectories addObject:[NSURL fileURLWithPath:path]];
  167. [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories;
  168. }
  169. - (void)unregisterPath:(NSString*)path
  170. {
  171. [_registeredDirectories removeObject:[NSURL fileURLWithPath:path]];
  172. [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories;
  173. }
  174. - (void)setString:(NSString*)key value:(NSString*)value
  175. {
  176. [_strings setObject:value forKey:key];
  177. }
  178. - (void)resetMenuItems
  179. {
  180. _menuItems = [[NSMutableArray alloc] init];
  181. }
  182. - (void)addMenuItem:(NSDictionary *)item {
  183. NSLog(@"Adding menu item.");
  184. [_menuItems addObject:item];
  185. }
  186. - (void)menuHasCompleted
  187. {
  188. NSLog(@"Emitting menu is complete signal now.");
  189. [self->_menuIsComplete signal];
  190. }
  191. - (void)connectionDidDie
  192. {
  193. [_strings removeAllObjects];
  194. [_registeredDirectories removeAllObjects];
  195. // For some reason the FIFinderSync cache doesn't seem to be cleared for the root item when
  196. // we reset the directoryURLs (seen on macOS 10.12 at least).
  197. // First setting it to the FS root and then setting it to nil seems to work around the issue.
  198. [FIFinderSyncController defaultController].directoryURLs = [NSSet setWithObject:[NSURL fileURLWithPath:@"/"]];
  199. // This will tell Finder that this extension isn't attached to any directory
  200. // until we can reconnect to the sync client.
  201. [FIFinderSyncController defaultController].directoryURLs = nil;
  202. }
  203. @end