Welcome to mirror list, hosted at ThFree Co, Russian Federation.

FinderSync.m « FinderSyncExt « OwnCloudFinderSync « MacOSX « shell_integration - github.com/owncloud/client.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 0f95465f26426486e531ccb3fc3101fa4abf7d36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*
 * Copyright (C) by Jocelyn Turcotte <jturcotte@woboq.com>
 *
 * 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

    NSURL *container = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:socketApiPrefix];
    NSURL *socketPath = [container URLByAppendingPathComponent:@"GUI.socket" isDirectory:false];

    if (socketPath.path) {
        self.v1LineProcessor = [[LineProcessorV1 alloc] initWithDelegate:self];
        self.localSocketClient = [[LocalSocketClient alloc] initWithSocketPath:socketPath.path
                                                                 lineProcessor:self.v1LineProcessor];
        [self.localSocketClient start];
    } else {
        self.localSocketClient = nil;
    }
    _registeredDirectories = [[NSMutableSet alloc] init];
    _strings = [[NSMutableDictionary alloc] init];
    _menuIsComplete = [[NSCondition alloc] init];

    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];
    [self.localSocketClient askForIcon:normalizedPath isDirectory:isDir];
}

#pragma mark - Menu and toolbar item support

- (NSString *)selectedPathsSeparatedByRecordSeparator
{
    FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
    NSMutableString *string = [[NSMutableString alloc] init];
    [syncController.selectedItemURLs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        if (string.length > 0) {
            [string appendString:@"\x1e"]; // record separator
        }
        NSString *normalizedPath = [[obj path] decomposedStringWithCanonicalMapping];
        [string appendString:normalizedPath];
    }];
    return string;
}

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

    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;
        }
    }];

    NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
    [self.localSocketClient askOnSocket:paths query:@"GET_MENU_ITEMS"];

    // The LocalSocketClient uses asynchronous communication, so we have to wait here until the menu items have
    // delivered by another thread.
    [self waitForMenuToArrive];

    id contextMenuTitle = [_strings objectForKey:@"CONTEXT_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"];

        // There is an annoying bug in macOS (at least 10.13.3), it does not use/copy over the representedObject of a menu item
        // So we have to use tag instead.
        int idx = 0;
        for (NSArray *item in _menuItems) {
            NSMenuItem *actionItem = [subMenu addItemWithTitle:[item valueForKey:@"text"]
                                                        action:@selector(subMenuActionClicked:)
                                                 keyEquivalent:@""];
            [actionItem setTag:idx];
            [actionItem setTarget:self];
            NSString *flags = [item valueForKey:@"flags"]; // e.g. "d"
            if ([flags rangeOfString:@"d"].location != NSNotFound) {
                [actionItem setEnabled:false];
            }
            idx++;
        }
        return menu;
    }
    return nil;
}

- (void)waitForMenuToArrive
{
    [self->_menuIsComplete lock];
    [self->_menuIsComplete wait];
    [self->_menuIsComplete unlock];
}

- (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];
}

#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)resetMenuItems
{
    _menuItems = [[NSMutableArray alloc] init];
}

- (void)addMenuItem:(NSDictionary *)item
{
    [_menuItems addObject:item];
}

- (void)menuHasCompleted
{
    [self->_menuIsComplete signal];
}

- (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