FinderSync.m 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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. FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
  20. NSBundle *extBundle = [NSBundle bundleForClass:[self class]];
  21. // This was added to the bundle's Info.plist to get it from the build system
  22. NSString *socketApiPrefix = [extBundle objectForInfoDictionaryKey:@"SocketApiPrefix"];
  23. NSImage *ok = [extBundle imageForResource:@"ok.icns"];
  24. NSImage *ok_swm = [extBundle imageForResource:@"ok_swm.icns"];
  25. NSImage *sync = [extBundle imageForResource:@"sync.icns"];
  26. NSImage *warning = [extBundle imageForResource:@"warning.icns"];
  27. NSImage *error = [extBundle imageForResource:@"error.icns"];
  28. [syncController setBadgeImage:ok label:@"Up to date" forBadgeIdentifier:@"OK"];
  29. [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC"];
  30. [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW"];
  31. [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE"];
  32. [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR"];
  33. [syncController setBadgeImage:ok_swm label:@"Shared" forBadgeIdentifier:@"OK+SWM"];
  34. [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC+SWM"];
  35. [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW+SWM"];
  36. [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE+SWM"];
  37. [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR+SWM"];
  38. // The Mach port name needs to:
  39. // - Be prefixed with the code signing Team ID
  40. // - Then infixed with the sandbox App Group
  41. // - The App Group itself must be a prefix of (or equal to) the application bundle identifier
  42. // We end up in the official signed client with: 9B5WD74GWJ.com.owncloud.desktopclient.socketApi
  43. // With ad-hoc signing (the '-' signing identity) we must drop the Team ID.
  44. // When the code isn't sandboxed (e.g. the OC client or the legacy overlay icon extension)
  45. // the OS doesn't seem to put any restriction on the port name, so we just follow what
  46. // the sandboxed App Extension needs.
  47. // https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24
  48. NSString *serverName = [socketApiPrefix stringByAppendingString:@".socketApi"];
  49. // NSLog(@"FinderSync serverName %@", serverName);
  50. _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName];
  51. _registeredDirectories = [[NSMutableSet alloc] init];
  52. _shareMenuTitle = nil;
  53. [_syncClientProxy start];
  54. return self;
  55. }
  56. #pragma mark - Primary Finder Sync protocol methods
  57. - (void)requestBadgeIdentifierForURL:(NSURL *)url
  58. {
  59. BOOL isDir;
  60. if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory: &isDir] == NO) {
  61. NSLog(@"ERROR: Could not determine file type of %@", [url path]);
  62. isDir = NO;
  63. }
  64. NSString* normalizedPath = [[url path] decomposedStringWithCanonicalMapping];
  65. [_syncClientProxy askForIcon:normalizedPath isDirectory:isDir];
  66. }
  67. #pragma mark - Menu and toolbar item support
  68. - (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu
  69. {
  70. FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
  71. NSMutableSet *rootPaths = [[NSMutableSet alloc] init];
  72. [syncController.directoryURLs enumerateObjectsUsingBlock: ^(id obj, BOOL *stop) {
  73. [rootPaths addObject:[obj path]];
  74. }];
  75. // The server doesn't support sharing a root directory so do not show the option in this case.
  76. // It is still possible to get a problematic sharing by selecting both the root and a child,
  77. // but this is so complicated to do and meaningless that it's not worth putting this check
  78. // also in shareMenuAction.
  79. __block BOOL onlyRootsSelected = YES;
  80. [syncController.selectedItemURLs enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
  81. if (![rootPaths member:[obj path]]) {
  82. onlyRootsSelected = NO;
  83. *stop = YES;
  84. }
  85. }];
  86. if (_shareMenuTitle && !onlyRootsSelected) {
  87. NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
  88. NSMenuItem *item = [menu addItemWithTitle:_shareMenuTitle action:@selector(shareMenuAction:) keyEquivalent:@"title"];
  89. item.image = [[NSBundle mainBundle] imageForResource:@"app.icns"];
  90. return menu;
  91. }
  92. return nil;
  93. }
  94. - (IBAction)shareMenuAction:(id)sender
  95. {
  96. NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
  97. [items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
  98. NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping];
  99. [_syncClientProxy askOnSocket:normalizedPath query:@"SHARE"];
  100. }];
  101. }
  102. #pragma mark - SyncClientProxyDelegate implementation
  103. - (void)setResultForPath:(NSString*)path result:(NSString*)result
  104. {
  105. NSString *normalizedPath = [path decomposedStringWithCanonicalMapping];
  106. [[FIFinderSyncController defaultController] setBadgeIdentifier:result forURL:[NSURL fileURLWithPath:normalizedPath]];
  107. }
  108. - (void)reFetchFileNameCacheForPath:(NSString*)path
  109. {
  110. }
  111. - (void)registerPath:(NSString*)path
  112. {
  113. assert(_registeredDirectories);
  114. [_registeredDirectories addObject:[NSURL fileURLWithPath:path]];
  115. [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories;
  116. }
  117. - (void)unregisterPath:(NSString*)path
  118. {
  119. [_registeredDirectories removeObject:[NSURL fileURLWithPath:path]];
  120. [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories;
  121. }
  122. - (void)setShareMenuTitle:(NSString*)title
  123. {
  124. _shareMenuTitle = title;
  125. }
  126. - (void)connectionDidDie
  127. {
  128. _shareMenuTitle = nil;
  129. [_registeredDirectories removeAllObjects];
  130. // For some reason the FIFinderSync cache doesn't seem to be cleared for the root item when
  131. // we reset the directoryURLs (seen on macOS 10.12 at least).
  132. // First setting it to the FS root and then setting it to nil seems to work around the issue.
  133. [FIFinderSyncController defaultController].directoryURLs = [NSSet setWithObject:[NSURL fileURLWithPath:@"/"]];
  134. // This will tell Finder that this extension isn't attached to any directory
  135. // until we can reconnect to the sync client.
  136. [FIFinderSyncController defaultController].directoryURLs = nil;
  137. }
  138. @end