diff --git a/shell_integration/MacOSX/OwnCloud.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/shell_integration/MacOSX/OwnCloud.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/shell_integration/MacOSX/OwnCloud.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h index 803c5270f..4cf5765bd 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h @@ -15,15 +15,20 @@ #import #import -#import "SyncClientProxy.h" +#import "SyncClient.h" +#import "LineProcessor.h" +#import "LocalSocketClient.h" -@interface FinderSync : FIFinderSync +@interface FinderSync : FIFinderSync { - 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 diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m index a0f791882..1da21a9d3 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m @@ -48,21 +48,32 @@ // - 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); + + NSURL *container = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:socketApiPrefix]; + NSURL *socketPath = [container URLByAppendingPathComponent:@".socket" isDirectory:NO]; + + NSLog(@"Socket path: %@", socketPath.path); - _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName]; - _registeredDirectories = [[NSMutableSet alloc] init]; - _strings = [[NSMutableDictionary alloc] init]; + 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]; - [_syncClientProxy start]; - return self; + 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 @@ -95,8 +106,19 @@ 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) { @@ -116,8 +138,11 @@ }]; 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) { @@ -151,7 +176,7 @@ 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 @@ -164,6 +189,7 @@ - (void)reFetchFileNameCacheForPath:(NSString*)path { + } - (void)registerPath:(NSString*)path @@ -189,9 +215,16 @@ _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 index 000000000..137ce62de --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LineProcessor.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 by Claudio Cambra + * + * 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 delegate; + +- (instancetype)initWithDelegate:(id)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 index 000000000..f67642e7c --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LineProcessor.m @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 by Claudio Cambra + * + * 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 +#import "LineProcessor.h" + +@implementation LineProcessor + +-(instancetype)initWithDelegate:(id)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 index 000000000..4a858cda2 --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LocalSocketClient.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 by Claudio Cambra + * + * 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 index 000000000..ad3cc1cde --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/LocalSocketClient.m @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2022 by Claudio Cambra + * + * 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 +#import "LocalSocketClient.h" +#include +#include +#include +#include + +@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", 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. 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. 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 index 000000000..f8c495a6c --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClient.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) by Jocelyn Turcotte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#import + +@protocol SyncClientDelegate +- (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 index 1d0fd74b8..000000000 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) by Jocelyn Turcotte - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#import - - -@protocol SyncClientProxyDelegate -- (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 -- (void)sendMessage:(NSData*)msg; -@end - -@interface SyncClientProxy : NSObject -{ - NSString *_serverName; - NSDistantObject *_remoteEnd; -} - -@property (weak) id 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 index 656f77003..000000000 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) by Jocelyn Turcotte - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#import "SyncClientProxy.h" - -@protocol ServerProtocol -- (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 *server = (NSDistantObject *)[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 *)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 - diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj index 5d987c86f..c055182bb 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj @@ -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 */ @@ -44,6 +45,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 539158A927BE606500816F56 /* LineProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LineProcessor.h; sourceTree = ""; }; + 539158AA27BE67CC00816F56 /* SyncClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SyncClient.h; sourceTree = ""; }; + 539158AB27BE71A900816F56 /* LineProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LineProcessor.m; sourceTree = ""; }; + 539158B127BE891500816F56 /* LocalSocketClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalSocketClient.h; sourceTree = ""; }; + 539158B227BEC98A00816F56 /* LocalSocketClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LocalSocketClient.m; sourceTree = ""; }; 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 = ""; }; C2B573B91B1CD91E00303B36 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; @@ -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 = ""; }; - C2C932EF1F0BFC6700C8BCB3 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -117,10 +121,13 @@ 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; @@ -186,7 +193,7 @@ C2B573951B1CD88000303B36 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0630; + LastUpgradeCheck = 1240; TargetAttributes = { C2B573B01B1CD91E00303B36 = { CreatedOnToolsVersion = 6.3.1; @@ -208,6 +215,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -275,7 +283,8 @@ 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; @@ -294,12 +303,65 @@ 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; }; diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme index 785a0da3a..3c65bbe5e 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme +++ b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme @@ -1,6 +1,6 @@ - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + - - 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) { diff --git a/src/gui/socketapi/socketapi.h b/src/gui/socketapi/socketapi.h index 112527bb3..11f8836f4 100644 --- a/src/gui/socketapi/socketapi.h +++ b/src/gui/socketapi/socketapi.h @@ -22,12 +22,7 @@ #include "config.h" -#if defined(Q_OS_MAC) -#include "socketapisocket_mac.h" -#else #include -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 _registeredAliases; QMap> _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 index 000000000..96aa83679 --- /dev/null +++ b/src/gui/socketapi/socketapi_mac.mm @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 by Claudio Cambra + * + * 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 +#import + +#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 index b76545351..000000000 --- a/src/gui/socketapi/socketapisocket_mac.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) by Jocelyn Turcotte - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#ifndef SOCKETAPISOCKET_OSX_H -#define SOCKETAPISOCKET_OSX_H - -#include -#include - -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 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 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 index 926c34d8a..000000000 --- a/src/gui/socketapi/socketapisocket_mac.mm +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) by Jocelyn Turcotte - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#include "socketapisocket_mac.h" -#import - -@protocol ChannelProtocol -- (void)sendMessage:(NSData *)msg; -@end - -@protocol RemoteEndProtocol -- (void)registerTransmitter:(id)tx; -@end - -@interface LocalEnd : NSObject -@property SocketApiSocketPrivate *wrapper; -- (instancetype)initWithWrapper:(SocketApiSocketPrivate *)wrapper; -@end - -@interface Server : NSObject -@property SocketApiServerPrivate *wrapper; -- (instancetype)initWithWrapper:(SocketApiServerPrivate *)wrapper; -- (void)registerClient:(NSDistantObject *)remoteEnd; -@end - -class SocketApiSocketPrivate -{ -public: - SocketApiSocket *q_ptr; - - SocketApiSocketPrivate(NSDistantObject *remoteEnd); - ~SocketApiSocketPrivate(); - - // release remoteEnd - void disconnectRemote(); - - NSDistantObject *remoteEnd; - LocalEnd *localEnd; - QByteArray inBuffer; - bool isRemoteDisconnected = false; -}; - -class SocketApiServerPrivate -{ -public: - SocketApiServer *q_ptr; - - SocketApiServerPrivate(); - ~SocketApiServerPrivate(); - - QList 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 *)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(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(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 *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]; -}