Implement local socket to communicate with finder extension
authorClaudio Cambra <claudio.cambra@gmail.com>
Wed, 23 Feb 2022 17:38:22 +0000 (18:38 +0100)
committerClaudio Cambra (Rebase PR Action) <claudio.cambra@gmail.com>
Thu, 17 Mar 2022 11:46:57 +0000 (11:46 +0000)
Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>
18 files changed:
shell_integration/MacOSX/OwnCloud.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist [new file with mode: 0644]
shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h
shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m
shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LineProcessor.h [new file with mode: 0644]
shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LineProcessor.m [new file with mode: 0644]
shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LocalSocketClient.h [new file with mode: 0644]
shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LocalSocketClient.m [new file with mode: 0644]
shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClient.h [new file with mode: 0644]
shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h [deleted file]
shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m [deleted file]
shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj
shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme
src/gui/socketapi/CMakeLists.txt
src/gui/socketapi/socketapi.cpp
src/gui/socketapi/socketapi.h
src/gui/socketapi/socketapi_mac.mm [new file with mode: 0644]
src/gui/socketapi/socketapisocket_mac.h [deleted file]
src/gui/socketapi/socketapisocket_mac.mm [deleted file]

diff --git a/shell_integration/MacOSX/OwnCloud.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/shell_integration/MacOSX/OwnCloud.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644 (file)
index 0000000..18d9810
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>IDEDidComputeMac32BitWarning</key>
+       <true/>
+</dict>
+</plist>
index 803c5270ffb2f5cdaa00b2317b4a9223633a2cde..4cf5765bd882d751578668b55ecbc9707325e998 100644 (file)
 
 #import <Cocoa/Cocoa.h>
 #import <FinderSync/FinderSync.h>
-#import "SyncClientProxy.h"
+#import "SyncClient.h"
+#import "LineProcessor.h"
+#import "LocalSocketClient.h"
 
-@interface FinderSync : FIFinderSync <SyncClientProxyDelegate>
+@interface FinderSync : FIFinderSync <SyncClientDelegate>
 {
-       SyncClientProxy *_syncClientProxy;
-       NSMutableSet *_registeredDirectories;
-       NSString *_shareMenuTitle;
-       NSMutableDictionary *_strings;
-       NSMutableArray *_menuItems;
+    NSMutableSet *_registeredDirectories;
+    NSString *_shareMenuTitle;
+    NSMutableDictionary *_strings;
+    NSMutableArray *_menuItems;
+    NSCondition *_menuIsComplete;
 }
 
+@property LineProcessor *lineProcessor;
+@property LocalSocketClient *localSocketClient;
+
 @end
index a0f791882c2b6aa34f473524a0d8c7c7f23a1918..1da21a9d3972baef983106eed3ac50061451ecb4 100644 (file)
        // - 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
+       // We end up in the official signed client with: 9B5WD74GWJ.com.owncloud.desktopclient.socket
        // 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;
+    
+    NSURL *container = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:socketApiPrefix];
+    NSURL *socketPath = [container URLByAppendingPathComponent:@".socket" isDirectory:NO];
+    
+    NSLog(@"Socket path: %@", socketPath.path);
+
+    if (socketPath.path) {
+        self.lineProcessor = [[LineProcessor alloc] initWithDelegate:self];
+        self.localSocketClient = [[LocalSocketClient alloc] init:socketPath.path
+                                                                 lineProcessor:self.lineProcessor];
+        [self.localSocketClient start];
+    } else {
+        NSLog(@"No socket path. Not initiating local socket client.");
+        self.localSocketClient = nil;
+    }
+    _registeredDirectories = [[NSMutableSet alloc] init];
+    _strings = [[NSMutableDictionary alloc] init];
+    _menuIsComplete = [[NSCondition alloc] init];
+
+    return self;
 }
 
 #pragma mark - Primary Finder Sync protocol methods
@@ -76,7 +87,7 @@
        }
 
        NSString* normalizedPath = [[url path] decomposedStringWithCanonicalMapping];
-       [_syncClientProxy askForIcon:normalizedPath isDirectory:isDir];
+       [self.localSocketClient askForIcon:normalizedPath isDirectory:isDir];
 }
 
 #pragma mark - Menu and toolbar item support
        return string;
 }
 
+- (void)waitForMenuToArrive
+{
+    [self->_menuIsComplete lock];
+    [self->_menuIsComplete wait];
+    [self->_menuIsComplete unlock];
+}
+
 - (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) {
        }];
 
        NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
-       // calling this IPC calls us back from client with several MENU_ITEM entries and then our askOnSocket returns again
-       [_syncClientProxy askOnSocket:paths query:@"GET_MENU_ITEMS"];
+       [self.localSocketClient askOnSocket:paths query:@"GET_MENU_ITEMS"];
+    
+    // Since the LocalSocketClient communicates asynchronously. wait here until the menu
+    // is delivered by another thread
+    [self waitForMenuToArrive];
 
        id contextMenuTitle = [_strings objectForKey:@"CONTEXT_MENU_TITLE"];
        if (contextMenuTitle && !onlyRootsSelected) {
        long idx = [(NSMenuItem*)sender tag];
        NSString *command = [[_menuItems objectAtIndex:idx] valueForKey:@"command"];
        NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
-       [_syncClientProxy askOnSocket:paths query:command];
+       [self.localSocketClient askOnSocket:paths query:command];
 }
 
 #pragma mark - SyncClientProxyDelegate implementation
 
 - (void)reFetchFileNameCacheForPath:(NSString*)path
 {
+    
 }
 
 - (void)registerPath:(NSString*)path
        _menuItems = [[NSMutableArray alloc] init];
 }
 - (void)addMenuItem:(NSDictionary *)item {
+    NSLog(@"Adding menu item.");
        [_menuItems addObject:item];
 }
 
+- (void)menuHasCompleted
+{
+    NSLog(@"Emitting menu is complete signal now.");
+    [self->_menuIsComplete signal];
+}
+
 - (void)connectionDidDie
 {
        [_strings removeAllObjects];
diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LineProcessor.h b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LineProcessor.h
new file mode 100644 (file)
index 0000000..137ce62
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.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 "SyncClient.h"
+
+#ifndef LineProcessor_h
+#define LineProcessor_h
+
+/// This class is in charge of dispatching all work that must be done on the UI side of the extension.
+/// Tasks are dispatched on the main UI thread for this reason.
+///
+/// These tasks are parsed from byte data (UTF9 strings) acquired from the socket; look at the
+/// LocalSocketClient for more detail on how data is read from and written to the socket.
+
+@interface LineProcessor : NSObject
+@property(nonatomic, weak)id<SyncClientDelegate> delegate;
+
+- (instancetype)initWithDelegate:(id<SyncClientDelegate>)delegate;
+- (void)process:(NSString*)line;
+
+@end
+#endif /* LineProcessor_h */
diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LineProcessor.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LineProcessor.m
new file mode 100644 (file)
index 0000000..f67642e
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.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 <Foundation/Foundation.h>
+#import "LineProcessor.h"
+
+@implementation LineProcessor
+
+-(instancetype)initWithDelegate:(id<SyncClientDelegate>)delegate
+{
+    NSLog(@"Init line processor with delegate.");
+    self.delegate = delegate;
+    return self;
+}
+
+-(void)process:(NSString*)line
+{
+    NSLog(@"Processing line: %@", line);
+    NSArray *split = [line componentsSeparatedByString:@":"];
+    NSString *command = [split objectAtIndex:0];
+    
+    NSLog(@"Command: %@", command);
+    
+    if([command isEqualToString:@"STATUS"]) {
+        NSString *result = [split objectAtIndex:1];
+        NSArray *pathSplit = [split subarrayWithRange:NSMakeRange(2, [split count] - 2)]; // Get everything after location 2
+        NSString *path = [pathSplit componentsJoinedByString:@":"];
+        
+        dispatch_async(dispatch_get_main_queue(), ^{
+            NSLog(@"Setting result %@ for path %@", result, path);
+            [self.delegate setResultForPath:path result:result];
+        });
+    } else if([command isEqualToString:@"UPDATE_VIEW"]) {
+        NSString *path = [split objectAtIndex:1];
+        
+        dispatch_async(dispatch_get_main_queue(), ^{
+            NSLog(@"Re-fetching filename cache for path %@", path);
+            [self.delegate reFetchFileNameCacheForPath:path];
+        });
+    } else if([command isEqualToString:@"REGISTER_PATH"]) {
+        NSString *path = [split objectAtIndex:1];
+        
+        dispatch_async(dispatch_get_main_queue(), ^{
+            NSLog(@"Registering path %@", path);
+            [self.delegate registerPath:path];
+        });
+    } else if([command isEqualToString:@"UNREGISTER_PATH"]) {
+        NSString *path = [split objectAtIndex:1];
+        
+        dispatch_async(dispatch_get_main_queue(), ^{
+            NSLog(@"Unregistering path %@", path);
+            [self.delegate unregisterPath:path];
+        });
+    } else if([command isEqualToString:@"GET_STRINGS"]) {
+        // BEGIN and END messages, do nothing.
+        return;
+    } else if([command isEqualToString:@"STRING"]) {
+        NSString *key = [split objectAtIndex:1];
+        NSString *value = [split objectAtIndex:2];
+        
+        dispatch_async(dispatch_get_main_queue(), ^{
+            NSLog(@"Setting string %@ to value %@", key, value);
+            [self.delegate setString:key value:value];
+        });
+    } else if([command isEqualToString:@"GET_MENU_ITEMS"]) {
+        if([[split objectAtIndex:1] isEqualToString:@"BEGIN"]) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                NSLog(@"Resetting menu items.");
+                [self.delegate resetMenuItems];
+            });
+        } else {
+            NSLog(@"Emitting menu has completed signal.");
+            [self.delegate menuHasCompleted];
+        }
+    } else if([command isEqualToString:@"MENU_ITEM"]) {
+        NSDictionary *item = @{@"command": [split objectAtIndex:1], @"flags": [split objectAtIndex:2], @"text": [split objectAtIndex:3]};
+        
+        dispatch_async(dispatch_get_main_queue(), ^{
+            NSLog(@"Adding menu item with command %@, flags %@, and text %@", [split objectAtIndex:1], [split objectAtIndex:2], [split objectAtIndex:3]);
+            [self.delegate addMenuItem:item];
+        });
+    } else {
+        // LOG UNKOWN COMMAND
+        NSLog(@"Unkown command: %@", command);
+    }
+}
+
+@end
diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LocalSocketClient.h b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LocalSocketClient.h
new file mode 100644 (file)
index 0000000..4a858cd
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.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 "LineProcessor.h"
+
+#ifndef LocalSocketClient_h
+#define LocalSocketClient_h
+#define BUF_SIZE 4096
+
+/// Class handling asynchronous communication with a server over a local UNIX socket.
+///
+/// The implementation uses a `DispatchQueue` and `DispatchSource`s to handle asynchronous communication and thread
+/// safety. The delegate that handles the line-decoding is **not invoked on the UI thread**, but the (random) thread associated
+/// with the `DispatchQueue`.
+///
+/// If any UI work needs to be done, the `LineProcessor` class dispatches this work on the main queue (so the UI thread) itself.
+///
+/// Other than the `init(withSocketPath:, lineProcessor)` and the `start()` method, all work is done "on the dispatch
+/// queue". The `localSocketQueue` is a serial dispatch queue (so a maximum of 1, and only 1, task is run at any
+/// moment), which guarantees safe access to instance variables. Both `askOnSocket(_:, query:)` and
+/// `askForIcon(_:, isDirectory:)` will internally dispatch the work on the `DispatchQueue`.
+///
+/// Sending and receiving data to and from the socket, is handled by two `DispatchSource`s. These will run an event
+/// handler when data can be read from resp. written to the socket. These handlers will also be run on the
+/// `DispatchQueue`.
+
+@interface LocalSocketClient : NSObject
+
+@property NSString* socketPath;
+@property LineProcessor* lineProcessor;
+@property int sock;
+@property dispatch_queue_t localSocketQueue;
+@property dispatch_source_t readSource;
+@property dispatch_source_t writeSource;
+@property NSMutableData* inBuffer;
+@property NSMutableData* outBuffer;
+
+- (instancetype)init:(NSString*)socketPath lineProcessor:(LineProcessor*)lineProcessor;
+- (BOOL)isConnected;
+- (void)start;
+- (void)restart;
+- (void)closeConnection;
+- (NSString*)strErr;
+- (void)askOnSocket:(NSString*)path query:(NSString*)verb;
+- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDirectory;
+- (void)readFromSocket;
+- (void)writeToSocket;
+- (void)processInBuffer;
+
+@end
+#endif /* LocalSocketClient_h */
diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LocalSocketClient.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LocalSocketClient.m
new file mode 100644 (file)
index 0000000..ad3cc1c
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.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 <Foundation/Foundation.h>
+#import "LocalSocketClient.h"
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <string.h>
+
+@implementation LocalSocketClient
+
+- (instancetype)init:(NSString*)socketPath lineProcessor:(LineProcessor*)lineProcessor
+{
+    NSLog(@"Initiating local socket client.");
+    self = [super init];
+    
+    if(self) {
+        self.socketPath = socketPath;
+        self.lineProcessor = lineProcessor;
+        
+        self.sock = -1;
+        self.localSocketQueue = dispatch_queue_create("localSocketQueue", DISPATCH_QUEUE_SERIAL);
+        
+        self.inBuffer = [NSMutableData data];
+        self.outBuffer = [NSMutableData data];
+    }
+        
+    return self;
+}
+
+- (BOOL)isConnected
+{
+    NSLog(@"Checking is connected: %@", self.sock != -1 ? @"YES" : @"NO");
+    return self.sock != -1;
+}
+
+- (void)start
+{
+    if([self isConnected]) {
+        NSLog(@"Socket client already connected. Not starting.");
+        return;
+    }
+    
+    struct sockaddr_un localSocketAddr;
+    unsigned long socketPathByteCount = [self.socketPath lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; // add 1 for the NUL terminator char
+    int maxByteCount = sizeof(localSocketAddr.sun_path);
+    
+    if(socketPathByteCount > maxByteCount) {
+        // LOG THAT THE SOCKET PATH IS TOO LONG HERE
+        NSLog(@"Socket path '%@' is too long: maximum socket path length is %i, this path is of length %lu", self.socketPath, maxByteCount, socketPathByteCount);
+        return;
+    }
+    
+    NSLog(@"Opening local socket...");
+    
+    // LOG THAT THE SOCKET IS BEING OPENED HERE
+    self.sock = socket(AF_LOCAL, SOCK_STREAM, 0);
+    
+    if(self.sock == -1) {
+        NSLog(@"Cannot open socket: '%@'", [self strErr]);
+        [self restart];
+        return;
+    }
+    
+    NSLog(@"Local socket opened. Connecting to '%@' ...", self.socketPath);
+    
+    localSocketAddr.sun_family = AF_LOCAL & 0xff;
+    
+    const char* pathBytes = [self.socketPath UTF8String];
+    strcpy(localSocketAddr.sun_path, pathBytes);
+    
+    int connectionStatus = connect(self.sock, (struct sockaddr*)&localSocketAddr, sizeof(localSocketAddr));
+    
+    if(connectionStatus == -1) {
+        NSLog(@"Could not connect to '%@': '%@'", self.socketPath, [self strErr]);
+        [self restart];
+        return;
+    }
+    
+    int flags = fcntl(self.sock, F_GETFL, 0);
+    
+    if(fcntl(self.sock, F_SETFL, flags | O_NONBLOCK) == -1) {
+        NSLog(@"Could not set socket to non-blocking mode: '%@'", [self strErr]);
+        [self restart];
+        return;
+    }
+    
+    NSLog(@"Connected to socket. Setting up dispatch sources...");
+    
+    self.readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self.sock, 0, self.localSocketQueue);
+    dispatch_source_set_event_handler(self.readSource, ^(void){ [self readFromSocket]; });
+    dispatch_source_set_cancel_handler(self.readSource, ^(void){
+        self.readSource = nil;
+        [self closeConnection];
+    });
+    
+    self.writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, self.sock, 0, self.localSocketQueue);
+    dispatch_source_set_event_handler(self.writeSource, ^(void){ [self writeToSocket]; });
+    dispatch_source_set_cancel_handler(self.writeSource, ^(void){
+        self.writeSource = nil;
+        [self closeConnection];
+    });
+    
+    // These dispatch sources are suspended upon creation.
+    // We resume the writeSource when we actually have something to write, suspending it again once our outBuffer is empty.
+    // We start the readSource now.
+    
+    NSLog(@"Starting to read from socket");
+    
+    dispatch_resume(self.readSource);
+    [self askOnSocket:@"" query:@"GET_STRINGS"];
+}
+
+- (void)restart
+{
+    NSLog(@"Restarting connection to socket.");
+    [self closeConnection];
+    dispatch_async(dispatch_get_main_queue(), ^(void){
+        [NSTimer scheduledTimerWithTimeInterval:5 repeats:NO block:^(NSTimer* timer) {
+            [self start];
+        }];
+    });
+}
+
+- (void)closeConnection
+{
+    NSLog(@"Closing connection.");
+    dispatch_source_cancel(self.readSource);
+    dispatch_source_cancel(self.writeSource);
+    self.readSource = nil;
+    self.writeSource = nil;
+    [self.inBuffer setLength:0];
+    [self.outBuffer setLength: 0];
+    
+    if(self.sock != -1) {
+        close(self.sock);
+        self.sock = -1;
+    }
+}
+
+- (NSString*)strErr
+{
+    int err = errno;
+    const char *errStr = strerror(err);
+    NSString *errorStr = [NSString stringWithUTF8String:errStr];
+    
+    if([errorStr length] == 0) {
+        return errorStr;
+    } else {
+        return [NSString stringWithFormat:@"Unknown error code: %i\10", err];
+    }
+}
+
+- (void)askOnSocket:(NSString *)path query:(NSString *)verb
+{
+    NSString *line = [NSString stringWithFormat:@"%@:%@\n", verb, path];
+    dispatch_async(self.localSocketQueue, ^(void) {
+        if(![self isConnected]) {
+            return;
+        }
+        
+        BOOL writeSourceIsSuspended = [self.outBuffer length] == 0;
+        
+        [self.outBuffer appendData:[line dataUsingEncoding:NSUTF8StringEncoding]];
+        
+        NSLog(@"Writing to out buffer: '%@'", line);
+        NSLog(@"Out buffer now %li bytes", [self.outBuffer length]);
+        
+        if(writeSourceIsSuspended) {
+            NSLog(@"Resuming write dispatch source.");
+            dispatch_resume(self.writeSource);
+        }
+    });
+}
+
+- (void)writeToSocket
+{
+    if(![self isConnected]) {
+        return;
+    }
+    
+    if([self.outBuffer length] == 0) {
+        NSLog(@"Empty out buffer, suspending write dispatch source.");
+        dispatch_suspend(self.writeSource);
+        return;
+    }
+    
+    NSLog(@"About to write %li bytes from outbuffer to socket.", [self.outBuffer length]);
+    
+    long bytesWritten = write(self.sock, [self.outBuffer bytes], [self.outBuffer length]);
+    char lineWritten[4096];
+    memcpy(lineWritten, [self.outBuffer bytes], [self.outBuffer length]);
+    NSLog(@"Wrote %li bytes to socket. Line was: '%@'", bytesWritten, [NSString stringWithUTF8String:lineWritten]);
+    
+    if(bytesWritten == 0) {
+        // 0 means we reached "end of file" and thus the socket was closed\10. So let's restart it
+        NSLog(@"Socket was closed. Restarting...");
+        [self restart];
+    } else if(bytesWritten == -1) {
+        int err = errno; // Make copy before it gets nuked by something else
+        
+        if(err == EAGAIN || err == EWOULDBLOCK)  {
+            // No free space in the OS' buffer, nothing to do here
+            NSLog(@"No free space in OS buffer. Ending write.");
+            return;
+        } else {
+            NSLog(@"Error writing to local socket: '%@'", [self strErr]);
+            [self restart];
+        }
+    } else if(bytesWritten > 0) {
+        [self.outBuffer replaceBytesInRange:NSMakeRange(0, bytesWritten) withBytes:NULL length:0];
+        
+        NSLog(@"Out buffer cleared. Now count is %li bytes.", [self.outBuffer length]);
+        
+        if([self.outBuffer length] == 0) {
+            NSLog(@"Out buffer has been emptied, suspending write dispatch source.");
+            dispatch_suspend(self.writeSource);
+        }
+    }
+}
+
+- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDirectory;
+{
+    NSLog(@"Asking for icon.");
+    
+    NSString *verb;
+    if(isDirectory) {
+        verb = @"RETRIEVE_FOLDER_STATUS";
+    } else {
+        verb = @"RETRIEVE_FILE_STATUS";
+    }
+    
+    [self askOnSocket:path query:verb];
+}
+
+- (void)readFromSocket
+{
+    if(![self isConnected]) {
+        return;
+    }
+    
+    NSLog(@"Reading from socket.");
+    
+    int bufferLength = BUF_SIZE / 2;
+    char buffer[bufferLength];
+    
+    while(true) {
+        long bytesRead = read(self.sock, buffer, bufferLength);
+        
+        NSLog(@"Read %li bytes from socket.", bytesRead);
+        
+        if(bytesRead == 0) {
+            // 0 means we reached "end of file" and thus the socket was closed\10. So let's restart it
+            NSLog(@"Socket was closed. Restarting...");
+            [self restart];
+            return;
+        } else if(bytesRead == -1) {
+            int err = errno;
+            if(err == EAGAIN) {
+                NSLog(@"No error and no data. Stopping.");
+                return; // No error, no data, so let's stop
+            } else {
+                NSLog(@"Error reading from local socket: '%@'", [self strErr]);
+                [self closeConnection];
+                return;
+            }
+        } else {
+            [self.inBuffer appendBytes:buffer length:bytesRead];
+            [self processInBuffer];
+        }
+    }
+}
+
+- (void)processInBuffer
+{
+    NSLog(@"Processing in buffer. In buffer length %li", [self.inBuffer length]);
+    UInt8 separator[] = {0xa}; // Byte value for "\n"
+    while(true) {
+        NSRange firstSeparatorIndex = [self.inBuffer rangeOfData:[NSData dataWithBytes:separator length:1] options:0 range:NSMakeRange(0, [self.inBuffer length])];
+        
+        if(firstSeparatorIndex.location == NSNotFound) {
+            NSLog(@"No separator found. Stopping.");
+            return; // No separator, nope out
+        } else {
+            unsigned char *buffer = [self.inBuffer mutableBytes];
+            buffer[firstSeparatorIndex.location] = 0; // Add NULL terminator, so we can use C string methods
+            
+            NSString *newLine = [NSString stringWithUTF8String:[self.inBuffer bytes]];
+
+            [self.inBuffer replaceBytesInRange:NSMakeRange(0, firstSeparatorIndex.location + 1) withBytes:NULL length:0];
+            [self.lineProcessor process:newLine];
+        }
+    }
+}
+    
+@end
diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClient.h b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClient.h
new file mode 100644 (file)
index 0000000..f8c495a
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+@protocol SyncClientDelegate <NSObject>
+- (void)setResultForPath:(NSString *)path result:(NSString *)result;
+- (void)reFetchFileNameCacheForPath:(NSString *)path;
+- (void)registerPath:(NSString *)path;
+- (void)unregisterPath:(NSString *)path;
+- (void)setString:(NSString *)key value:(NSString *)value;
+- (void)resetMenuItems;
+- (void)addMenuItem:(NSDictionary *)item;
+- (void)menuHasCompleted;
+- (void)connectionDidDie;
+@end
diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h
deleted file mode 100644 (file)
index 1d0fd74..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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 <Foundation/Foundation.h>
-
-
-@protocol SyncClientProxyDelegate <NSObject>
-- (void)setResultForPath:(NSString*)path result:(NSString*)result;
-- (void)reFetchFileNameCacheForPath:(NSString*)path;
-- (void)registerPath:(NSString*)path;
-- (void)unregisterPath:(NSString*)path;
-- (void)setString:(NSString*)key value:(NSString*)value;
-- (void)resetMenuItems;
-- (void)addMenuItem:(NSDictionary *)item;
-- (void)connectionDidDie;
-@end
-
-@protocol ChannelProtocol <NSObject>
-- (void)sendMessage:(NSData*)msg;
-@end
-
-@interface SyncClientProxy : NSObject <ChannelProtocol>
-{
-       NSString *_serverName;
-       NSDistantObject <ChannelProtocol> *_remoteEnd;
-}
-
-@property (weak) id <SyncClientProxyDelegate> delegate;
-
-- (instancetype)initWithDelegate:(id)arg1 serverName:(NSString*)serverName;
-- (void)start;
-- (void)askOnSocket:(NSString*)path query:(NSString*)verb;
-- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir;
-@end
diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m
deleted file mode 100644 (file)
index 656f770..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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 "SyncClientProxy.h"
-
-@protocol ServerProtocol <NSObject>
-- (void)registerClient:(id)client;
-@end
-
-@interface SyncClientProxy ()
-- (void)registerTransmitter:(id)tx;
-@end
-
-@implementation SyncClientProxy
-
-- (instancetype)initWithDelegate:(id)arg1 serverName:(NSString*)serverName
-{
-       self = [super init];
-       
-       self.delegate = arg1;
-       _serverName = serverName;
-       _remoteEnd = nil;
-
-       return self;
-}
-
-#pragma mark - Connection setup
-
-- (void)start
-{
-       if (_remoteEnd)
-               return;
-
-       // Lookup the server connection
-       NSConnection *conn = [NSConnection connectionWithRegisteredName:_serverName host:nil];
-
-       if (!conn) {
-               // Could not connect to the sync client
-               [self scheduleRetry];
-               return;
-       }
-
-       [[NSNotificationCenter defaultCenter] addObserver:self
-                                                                                        selector:@selector(connectionDidDie:)
-                                                                                                name:NSConnectionDidDieNotification
-                                                                                          object:conn];
-       
-       NSDistantObject <ServerProtocol> *server = (NSDistantObject <ServerProtocol> *)[conn rootProxy];
-       assert(server);
-       
-       // This saves a few Mach messages, enable "Distributed Objects" in the scheme's Run diagnostics to watch
-       [server setProtocolForProxy:@protocol(ServerProtocol)];
-       
-       // Send an object to the server to act as the channel rx, we'll receive the tx through registerTransmitter
-       [server registerClient:self];
-}
-
-- (void)registerTransmitter:(id)tx;
-{
-       // The server replied with the distant object that we will use for tx
-       _remoteEnd = (NSDistantObject <ChannelProtocol> *)tx;
-       [_remoteEnd setProtocolForProxy:@protocol(ChannelProtocol)];
-       
-       // Everything is set up, start querying
-       [self askOnSocket:@"" query:@"GET_STRINGS"];
-}
-
-- (void)scheduleRetry
-{
-       [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(start) userInfo:nil repeats:NO];
-}
-
-- (void)connectionDidDie:(NSNotification*)notification
-{
-#pragma unused(notification)   
-       _remoteEnd = nil;
-       [_delegate connectionDidDie];
-       
-       [self scheduleRetry];
-}
-
-#pragma mark - Communication logic
-
-- (void)sendMessage:(NSData*)msg
-{
-       NSString *answer = [[NSString alloc] initWithData:msg encoding:NSUTF8StringEncoding];
-       
-       // Cut the trailing newline. We always only receive one line from the client.
-       answer = [answer substringToIndex:[answer length] - 1];
-       NSArray *chunks = [answer componentsSeparatedByString: @":"];
-       
-       if( [[chunks objectAtIndex:0] isEqualToString:@"STATUS"] ) {
-               NSString *result = [chunks objectAtIndex:1];
-               NSString *path = [chunks objectAtIndex:2];
-               if( [chunks count] > 3 ) {
-                       for( int i = 2; i < [chunks count]-1; i++ ) {
-                               path = [NSString stringWithFormat:@"%@:%@",
-                                               path, [chunks objectAtIndex:i+1] ];
-                       }
-               }
-               [_delegate setResultForPath:path result:result];
-       } else if( [[chunks objectAtIndex:0] isEqualToString:@"UPDATE_VIEW"] ) {
-               NSString *path = [chunks objectAtIndex:1];
-               [_delegate reFetchFileNameCacheForPath:path];
-       } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"REGISTER_PATH"] ) {
-               NSString *path = [chunks objectAtIndex:1];
-               [_delegate registerPath:path];
-       } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"UNREGISTER_PATH"] ) {
-               NSString *path = [chunks objectAtIndex:1];
-               [_delegate unregisterPath:path];
-       } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"GET_STRINGS"] ) {
-               // BEGIN and END messages, do nothing.
-       } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"STRING"] ) {
-               [_delegate setString:[chunks objectAtIndex:1] value:[chunks objectAtIndex:2]];
-       } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"GET_MENU_ITEMS"] ) {
-               if ([[chunks objectAtIndex:1] isEqualToString:@"BEGIN"]) {
-                       [_delegate resetMenuItems];
-               } else if ([[chunks objectAtIndex:1] isEqualToString:@"END"]) {
-                       // Don't do anything special, the askOnSocket call in FinderSync menuForMenuKind will return after this line
-               }
-       } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"MENU_ITEM"] ) {
-               NSMutableDictionary *item = [[NSMutableDictionary alloc] init];
-               [item setValue:[chunks objectAtIndex:1] forKey:@"command"]; // e.g. "COPY_PRIVATE_LINK"
-               [item setValue:[chunks objectAtIndex:2] forKey:@"flags"]; // e.g. "d"
-               [item setValue:[chunks objectAtIndex:3] forKey:@"text"]; // e.g. "Copy private link to clipboard"
-               [_delegate addMenuItem:item];
-       } else {
-               NSLog(@"SyncState: Unknown command %@", [chunks objectAtIndex:0]);
-       }
-}
-
-- (void)askOnSocket:(NSString*)path query:(NSString*)verb
-{
-       NSString *query = [NSString stringWithFormat:@"%@:%@\n", verb,path];
-       
-       @try {
-               [_remoteEnd sendMessage:[query dataUsingEncoding:NSUTF8StringEncoding]];
-       } @catch(NSException* e) {
-               // Do nothing and wait for connectionDidDie
-       }
-}
-
-- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir
-{
-       NSString *verb = isDir ? @"RETRIEVE_FOLDER_STATUS" : @"RETRIEVE_FILE_STATUS";
-       [self askOnSocket:path query:verb];
-}
-
-@end
-
index 5d987c86f2bb8b7485dc5cb21075eab858988a44..c055182bbd15a56f7125cf73de5ca7cf634fe4e0 100644 (file)
@@ -7,6 +7,8 @@
        objects = {
 
 /* Begin PBXBuildFile section */
+               539158AC27BE71A900816F56 /* LineProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 539158AB27BE71A900816F56 /* LineProcessor.m */; };
+               539158B327BEC98A00816F56 /* LocalSocketClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 539158B227BEC98A00816F56 /* LocalSocketClient.m */; };
                C2B573BA1B1CD91E00303B36 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573B91B1CD91E00303B36 /* main.m */; };
                C2B573D21B1CD94B00303B36 /* main.m in Resources */ = {isa = PBXBuildFile; fileRef = C2B573B91B1CD91E00303B36 /* main.m */; };
                C2B573DE1B1CD9CE00303B36 /* FinderSync.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573DD1B1CD9CE00303B36 /* FinderSync.m */; };
@@ -16,7 +18,6 @@
                C2B573F51B1DAD6400303B36 /* ok.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573ED1B1DAD6400303B36 /* ok.iconset */; };
                C2B573F71B1DAD6400303B36 /* sync.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573EF1B1DAD6400303B36 /* sync.iconset */; };
                C2B573F91B1DAD6400303B36 /* warning.iconset in Resources */ = {isa = PBXBuildFile; fileRef = C2B573F11B1DAD6400303B36 /* warning.iconset */; };
-               C2C932F01F0BFC6700C8BCB3 /* SyncClientProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C2C932EF1F0BFC6700C8BCB3 /* SyncClientProxy.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+               539158A927BE606500816F56 /* LineProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LineProcessor.h; sourceTree = "<group>"; };
+               539158AA27BE67CC00816F56 /* SyncClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SyncClient.h; sourceTree = "<group>"; };
+               539158AB27BE71A900816F56 /* LineProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LineProcessor.m; sourceTree = "<group>"; };
+               539158B127BE891500816F56 /* LocalSocketClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalSocketClient.h; sourceTree = "<group>"; };
+               539158B227BEC98A00816F56 /* LocalSocketClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LocalSocketClient.m; sourceTree = "<group>"; };
                C2B573B11B1CD91E00303B36 /* desktopclient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktopclient.app; sourceTree = BUILT_PRODUCTS_DIR; };
                C2B573B51B1CD91E00303B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
                C2B573B91B1CD91E00303B36 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@@ -57,8 +63,6 @@
                C2B573ED1B1DAD6400303B36 /* ok.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = ok.iconset; path = ../../icons/nopadding/ok.iconset; sourceTree = SOURCE_ROOT; };
                C2B573EF1B1DAD6400303B36 /* sync.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = sync.iconset; path = ../../icons/nopadding/sync.iconset; sourceTree = SOURCE_ROOT; };
                C2B573F11B1DAD6400303B36 /* warning.iconset */ = {isa = PBXFileReference; lastKnownFileType = folder.iconset; name = warning.iconset; path = ../../icons/nopadding/warning.iconset; sourceTree = SOURCE_ROOT; };
-               C2C932EE1F0BFC6700C8BCB3 /* SyncClientProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SyncClientProxy.h; sourceTree = "<group>"; };
-               C2C932EF1F0BFC6700C8BCB3 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
                C2B573D81B1CD9CE00303B36 /* FinderSyncExt */ = {
                        isa = PBXGroup;
                        children = (
-                               C2C932EE1F0BFC6700C8BCB3 /* SyncClientProxy.h */,
-                               C2C932EF1F0BFC6700C8BCB3 /* SyncClientProxy.m */,
+                               539158AA27BE67CC00816F56 /* SyncClient.h */,
                                C2B573DC1B1CD9CE00303B36 /* FinderSync.h */,
                                C2B573DD1B1CD9CE00303B36 /* FinderSync.m */,
+                               539158A927BE606500816F56 /* LineProcessor.h */,
+                               539158AB27BE71A900816F56 /* LineProcessor.m */,
+                               539158B127BE891500816F56 /* LocalSocketClient.h */,
+                               539158B227BEC98A00816F56 /* LocalSocketClient.m */,
                                C2B573D91B1CD9CE00303B36 /* Supporting Files */,
                        );
                        path = FinderSyncExt;
                C2B573951B1CD88000303B36 /* Project object */ = {
                        isa = PBXProject;
                        attributes = {
-                               LastUpgradeCheck = 0630;
+                               LastUpgradeCheck = 1240;
                                TargetAttributes = {
                                        C2B573B01B1CD91E00303B36 = {
                                                CreatedOnToolsVersion = 6.3.1;
                        developmentRegion = English;
                        hasScannedForEncodings = 0;
                        knownRegions = (
+                               English,
                                en,
                                Base,
                        );
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
-                               C2C932F01F0BFC6700C8BCB3 /* SyncClientProxy.m in Sources */,
+                               539158B327BEC98A00816F56 /* LocalSocketClient.m in Sources */,
+                               539158AC27BE71A900816F56 /* LineProcessor.m in Sources */,
                                C2B573DE1B1CD9CE00303B36 /* FinderSync.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                C2B573991B1CD88000303B36 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               ENABLE_TESTABILITY = YES;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
                        };
                        name = Debug;
                };
                C2B5739A1B1CD88000303B36 /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
                        };
                        name = Release;
                };
index 785a0da3a4b32091fe0ba5cb46735621d6d58c55..3c65bbe5e119b66b0db20f9a62f14b06e3c60127 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0630"
+   LastUpgradeVersion = "1240"
    wasCreatedForAppExtension = "YES"
    version = "2.0">
    <BuildAction
       </BuildActionEntries>
    </BuildAction>
    <TestAction
+      buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      buildConfiguration = "Debug">
-      <Testables>
-      </Testables>
+      shouldUseLaunchSchemeArgsEnv = "YES">
       <MacroExpansion>
          <BuildableReference
             BuildableIdentifier = "primary"
             ReferencedContainer = "container:OwnCloudFinderSync.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
+      <Testables>
+      </Testables>
    </TestAction>
    <LaunchAction
+      buildConfiguration = "Debug"
       selectedDebuggerIdentifier = ""
       selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
       launchStyle = "0"
+      askForAppToLaunch = "Yes"
       useCustomWorkingDirectory = "NO"
-      buildConfiguration = "Debug"
       ignoresPersistentStateOnLaunch = "NO"
       debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
       allowLocationSimulation = "YES"
       launchAutomaticallySubstyle = "2">
       <BuildableProductRunnable
             ReferencedContainer = "container:OwnCloudFinderSync.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
+      buildConfiguration = "Release"
       shouldUseLaunchSchemeArgsEnv = "YES"
       savedToolIdentifier = ""
       useCustomWorkingDirectory = "NO"
-      buildConfiguration = "Release"
       debugDocumentVersioning = "YES"
       launchAutomaticallySubstyle = "2">
       <BuildableProductRunnable
index a84933d90e7af2bd6d14ceb0074173de9f1fc6b5..e010b56c0532996aa2869b031734480d05530a64 100644 (file)
@@ -6,5 +6,5 @@ target_sources(nextcloudCore PRIVATE
 )
 
 if( APPLE )
-    target_sources(nextcloudCore PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/socketapisocket_mac.mm)
+    target_sources(nextcloudCore PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/socketapi_mac.mm)
 endif()
index 88d8e28520d7a14767538c9cb390a0c401e5ed98..d05c75006d43566fcb1da0c8e4e4648213863821 100644 (file)
@@ -237,11 +237,8 @@ SocketApi::SocketApi(QObject *parent)
         // See issue #2388
         // + Theme::instance()->appName();
     } else if (Utility::isMac()) {
-        // This must match the code signing Team setting of the extension
-        // Example for developer builds (with ad-hoc signing identity): "" "com.owncloud.desktopclient" ".socketApi"
-        // Example for official signed packages: "9B5WD74GWJ." "com.owncloud.desktopclient" ".socketApi"
-        socketPath = SOCKETAPI_TEAM_IDENTIFIER_PREFIX APPLICATION_REV_DOMAIN ".socketApi";
-#ifdef Q_OS_MAC
+#ifdef Q_OS_MACOS
+        socketPath = socketApiSocketPath();
         CFURLRef url = (CFURLRef)CFAutorelease((CFURLRef)CFBundleCopyBundleURL(CFBundleGetMainBundle()));
         QString bundlePath = QUrl::fromCFURL(url).path();
 
@@ -269,14 +266,19 @@ SocketApi::SocketApi(QObject *parent)
         qCWarning(lcSocketApi) << "An unexpected system detected, this probably won't work.";
     }
 
-    SocketApiServer::removeServer(socketPath);
-    QFileInfo info(socketPath);
-    if (!info.dir().exists()) {
-        bool result = info.dir().mkpath(".");
-        qCDebug(lcSocketApi) << "creating" << info.dir().path() << result;
-        if (result) {
-            QFile::setPermissions(socketPath,
-                QFile::Permissions(QFile::ReadOwner + QFile::WriteOwner + QFile::ExeOwner));
+    QLocalServer::removeServer(socketPath);
+    // Create the socket path:
+    if (!Utility::isMac()) {
+        // Not on macOS: there the directory is there, and created for us by the sandboxing
+        // environment, because we belong to an App Group.
+        QFileInfo info(socketPath);
+        if (!info.dir().exists()) {
+            bool result = info.dir().mkpath(".");
+            qCDebug(lcSocketApi) << "creating" << info.dir().path() << result;
+            if (result) {
+                QFile::setPermissions(socketPath,
+                    QFile::Permissions(QFile::ReadOwner + QFile::WriteOwner + QFile::ExeOwner));
+            }
         }
     }
     if (!_localServer.listen(socketPath)) {
@@ -285,7 +287,7 @@ SocketApi::SocketApi(QObject *parent)
         qCInfo(lcSocketApi) << "server started, listening at " << socketPath;
     }
 
-    connect(&_localServer, &SocketApiServer::newConnection, this, &SocketApi::slotNewConnection);
+    connect(&_localServer, &QLocalServer::newConnection, this, &SocketApi::slotNewConnection);
 
     // folder watcher
     connect(FolderMan::instance(), &FolderMan::folderSyncStateChange, this, &SocketApi::slotUpdateFolderView);
@@ -302,8 +304,6 @@ SocketApi::~SocketApi()
 
 void SocketApi::slotNewConnection()
 {
-    // Note that on macOS this is not actually a line-based QIODevice, it's a SocketApiSocket which is our
-    // custom message based macOS IPC.
     QIODevice *socket = _localServer.nextPendingConnection();
 
     if (!socket) {
index 112527bb374510fbbc834ffc41bc318f9e03e92f..11f8836f46108c7d21cfa6b6b8b8f11bdd46c46d 100644 (file)
 
 #include "config.h"
 
-#if defined(Q_OS_MAC)
-#include "socketapisocket_mac.h"
-#else
 #include <QLocalServer>
-using SocketApiServer = QLocalServer;
-#endif
 
 class QUrl;
 class QLocalSocket;
@@ -44,6 +39,10 @@ class SocketApiJobV2;
 
 Q_DECLARE_LOGGING_CATEGORY(lcSocketApi)
 
+#ifdef Q_OS_MACOS
+QString socketApiSocketPath();
+#endif
+
 /**
  * @brief The SocketApi class
  * @ingroup gui
@@ -173,7 +172,7 @@ private:
 
     QSet<QString> _registeredAliases;
     QMap<QIODevice *, QSharedPointer<SocketListener>> _listeners;
-    SocketApiServer _localServer;
+    QLocalServer _localServer;
 };
 }
 
diff --git a/src/gui/socketapi/socketapi_mac.mm b/src/gui/socketapi/socketapi_mac.mm
new file mode 100644 (file)
index 0000000..96aa836
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.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 <Cocoa/Cocoa.h>
+#import <QString>
+
+#include "application.h"
+
+namespace OCC
+{
+
+QString socketApiSocketPath()
+{
+    // This must match the code signing Team setting of the extension
+    // Example for developer builds (with ad-hoc signing identity): "" "com.owncloud.desktopclient" ".socket"
+    // Example for official signed packages: "9B5WD74GWJ." "com.owncloud.desktopclient" ".socket"
+    NSString *appGroupId = @SOCKETAPI_TEAM_IDENTIFIER_PREFIX APPLICATION_REV_DOMAIN;
+
+    NSURL *container = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupId];
+    NSURL *socketPath = [container URLByAppendingPathComponent:@".socket" isDirectory:false];
+    return QString::fromNSString(socketPath.path);
+}
+
+}
diff --git a/src/gui/socketapi/socketapisocket_mac.h b/src/gui/socketapi/socketapisocket_mac.h
deleted file mode 100644 (file)
index b765453..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef SOCKETAPISOCKET_OSX_H
-#define SOCKETAPISOCKET_OSX_H
-
-#include <QAbstractSocket>
-#include <QIODevice>
-
-class SocketApiServerPrivate;
-class SocketApiSocketPrivate;
-
-class SocketApiSocket : public QIODevice
-{
-    Q_OBJECT
-public:
-    SocketApiSocket(QObject *parent, SocketApiSocketPrivate *p);
-    ~SocketApiSocket();
-
-    qint64 readData(char *data, qint64 maxlen) override;
-    qint64 writeData(const char *data, qint64 len) override;
-
-    bool isSequential() const override { return true; }
-    qint64 bytesAvailable() const override;
-    bool canReadLine() const override;
-
-signals:
-    void disconnected();
-
-private:
-    // Use Qt's p-impl system to hide objective-c types from C++ code including this file
-    Q_DECLARE_PRIVATE(SocketApiSocket)
-    QScopedPointer<SocketApiSocketPrivate> d_ptr;
-    friend class SocketApiServerPrivate;
-};
-
-class SocketApiServer : public QObject
-{
-    Q_OBJECT
-public:
-    SocketApiServer();
-    ~SocketApiServer();
-
-    void close();
-    bool listen(const QString &name);
-    SocketApiSocket *nextPendingConnection();
-
-    static bool removeServer(const QString &) { return false; }
-
-signals:
-    void newConnection();
-
-private:
-    Q_DECLARE_PRIVATE(SocketApiServer)
-    QScopedPointer<SocketApiServerPrivate> d_ptr;
-};
-
-#endif // SOCKETAPISOCKET_OSX_H
diff --git a/src/gui/socketapi/socketapisocket_mac.mm b/src/gui/socketapi/socketapisocket_mac.mm
deleted file mode 100644 (file)
index 926c34d..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * 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.
- */
-
-#include "socketapisocket_mac.h"
-#import <Cocoa/Cocoa.h>
-
-@protocol ChannelProtocol <NSObject>
-- (void)sendMessage:(NSData *)msg;
-@end
-
-@protocol RemoteEndProtocol <NSObject, ChannelProtocol>
-- (void)registerTransmitter:(id)tx;
-@end
-
-@interface LocalEnd : NSObject <ChannelProtocol>
-@property SocketApiSocketPrivate *wrapper;
-- (instancetype)initWithWrapper:(SocketApiSocketPrivate *)wrapper;
-@end
-
-@interface Server : NSObject
-@property SocketApiServerPrivate *wrapper;
-- (instancetype)initWithWrapper:(SocketApiServerPrivate *)wrapper;
-- (void)registerClient:(NSDistantObject<RemoteEndProtocol> *)remoteEnd;
-@end
-
-class SocketApiSocketPrivate
-{
-public:
-    SocketApiSocket *q_ptr;
-
-    SocketApiSocketPrivate(NSDistantObject<ChannelProtocol> *remoteEnd);
-    ~SocketApiSocketPrivate();
-
-    // release remoteEnd
-    void disconnectRemote();
-
-    NSDistantObject<ChannelProtocol> *remoteEnd;
-    LocalEnd *localEnd;
-    QByteArray inBuffer;
-    bool isRemoteDisconnected = false;
-};
-
-class SocketApiServerPrivate
-{
-public:
-    SocketApiServer *q_ptr;
-
-    SocketApiServerPrivate();
-    ~SocketApiServerPrivate();
-
-    QList<SocketApiSocket *> pendingConnections;
-    NSConnection *connection;
-    Server *server;
-};
-
-
-@implementation LocalEnd
-- (instancetype)initWithWrapper:(SocketApiSocketPrivate *)wrapper
-{
-    self = [super init];
-    self->_wrapper = wrapper;
-    return self;
-}
-
-- (void)sendMessage:(NSData *)msg
-{
-    if (_wrapper) {
-        _wrapper->inBuffer += QByteArray::fromRawNSData(msg);
-        emit _wrapper->q_ptr->readyRead();
-    }
-}
-
-- (void)connectionDidDie:(NSNotification *)notification
-{
-#pragma unused(notification)
-    if (_wrapper) {
-        _wrapper->disconnectRemote();
-        emit _wrapper->q_ptr->disconnected();
-    }
-}
-@end
-
-@implementation Server
-- (instancetype)initWithWrapper:(SocketApiServerPrivate *)wrapper
-{
-    self = [super init];
-    self->_wrapper = wrapper;
-    return self;
-}
-
-- (void)registerClient:(NSDistantObject<RemoteEndProtocol> *)remoteEnd
-{
-    // This saves a few mach messages that would otherwise be needed to query the interface
-    [remoteEnd setProtocolForProxy:@protocol(RemoteEndProtocol)];
-
-    SocketApiServer *server = _wrapper->q_ptr;
-    SocketApiSocketPrivate *socketPrivate = new SocketApiSocketPrivate(remoteEnd);
-    SocketApiSocket *socket = new SocketApiSocket(server, socketPrivate);
-    _wrapper->pendingConnections.append(socket);
-    emit server->newConnection();
-
-    [remoteEnd registerTransmitter:socketPrivate->localEnd];
-}
-@end
-
-
-SocketApiSocket::SocketApiSocket(QObject *parent, SocketApiSocketPrivate *p)
-    : QIODevice(parent)
-    , d_ptr(p)
-{
-    Q_D(SocketApiSocket);
-    d->q_ptr = this;
-    open(ReadWrite);
-}
-
-SocketApiSocket::~SocketApiSocket()
-{
-}
-
-qint64 SocketApiSocket::readData(char *data, qint64 maxlen)
-{
-    Q_D(SocketApiSocket);
-    qint64 len = std::min(maxlen, static_cast<qint64>(d->inBuffer.size()));
-    memcpy(data, d->inBuffer.constData(), len);
-    d->inBuffer.remove(0, len);
-    return len;
-}
-
-qint64 SocketApiSocket::writeData(const char *data, qint64 len)
-{
-    Q_D(SocketApiSocket);
-    if (d->isRemoteDisconnected)
-        return -1;
-
-    @try {
-        // FIXME: The NSConnection will make this block unless the function is marked as "oneway"
-        // in the protocol. This isn't async and reduces our performances but this currectly avoids
-        // a Mach queue deadlock during requests bursts of the legacy OwnCloudFinder extension.
-        // Since FinderSync already runs in a separate process, blocking isn't too critical.
-        [d->remoteEnd sendMessage:[NSData dataWithBytesNoCopy:const_cast<char *>(data) length:len freeWhenDone:NO]];
-        return len;
-    } @catch (NSException *e) {
-        // connectionDidDie can be notified too late, also interpret any sending exception as a disconnection.
-        d->disconnectRemote();
-        emit disconnected();
-        return -1;
-    }
-}
-
-qint64 SocketApiSocket::bytesAvailable() const
-{
-    Q_D(const SocketApiSocket);
-    return d->inBuffer.size() + QIODevice::bytesAvailable();
-}
-
-bool SocketApiSocket::canReadLine() const
-{
-    Q_D(const SocketApiSocket);
-    return d->inBuffer.indexOf('\n', int(pos())) != -1 || QIODevice::canReadLine();
-}
-
-SocketApiSocketPrivate::SocketApiSocketPrivate(NSDistantObject<ChannelProtocol> *remoteEnd)
-    : remoteEnd(remoteEnd)
-    , localEnd([[LocalEnd alloc] initWithWrapper:this])
-{
-    [remoteEnd retain];
-    // (Ab)use our objective-c object just to catch the notification
-    [[NSNotificationCenter defaultCenter] addObserver:localEnd
-                                             selector:@selector(connectionDidDie:)
-                                                 name:NSConnectionDidDieNotification
-                                               object:[remoteEnd connectionForProxy]];
-}
-
-SocketApiSocketPrivate::~SocketApiSocketPrivate()
-{
-    disconnectRemote();
-
-    // The DO vended localEnd might still be referenced by the connection
-    localEnd.wrapper = nil;
-    [localEnd release];
-}
-
-void SocketApiSocketPrivate::disconnectRemote()
-{
-    if (isRemoteDisconnected)
-        return;
-    isRemoteDisconnected = true;
-
-    [remoteEnd release];
-}
-
-SocketApiServer::SocketApiServer()
-    : d_ptr(new SocketApiServerPrivate)
-{
-    Q_D(SocketApiServer);
-    d->q_ptr = this;
-}
-
-SocketApiServer::~SocketApiServer()
-{
-}
-
-void SocketApiServer::close()
-{
-    // Assume we'll be destroyed right after
-}
-
-bool SocketApiServer::listen(const QString &name)
-{
-    Q_D(SocketApiServer);
-    // Set the name of the root object
-    return [d->connection registerName:name.toNSString()];
-}
-
-SocketApiSocket *SocketApiServer::nextPendingConnection()
-{
-    Q_D(SocketApiServer);
-    return d->pendingConnections.takeFirst();
-}
-
-SocketApiServerPrivate::SocketApiServerPrivate()
-{
-    // Create the connection and server object to vend over Disributed Objects
-    connection = [[NSConnection alloc] init];
-    server = [[Server alloc] initWithWrapper:this];
-    [connection setRootObject:server];
-}
-
-SocketApiServerPrivate::~SocketApiServerPrivate()
-{
-    [connection release];
-    server.wrapper = nil;
-    [server release];
-}