shell_i: Use an NSConnection instead of a local socket #2340

This prepares the switch to the official FinderSync API on Yosemite
which requires the extension to run in a sandbox. This complicates
the usage of a local socket to communicate with a non-sandboxed GUI
client. An NSConnection is easier to use in this case, which we can
use as long as the server name (i.e. Mach port registered name) is
prefixed with the code signing Team Identifier.

A placeholder server implementation is also added to the client's
SocketApi which basically reproduces the interface of a QLocalSocket.
Most of the references to individual sockets we're only using
QIODevice methods so the type was simply reduced. A typedef to
replace the QLocalServer was the only other part needed.
This commit is contained in:
Jocelyn Turcotte 2015-06-15 14:51:11 +02:00
parent 758a820b0c
commit bfcfdeec64
13 changed files with 584 additions and 9299 deletions

View file

@ -149,11 +149,11 @@ static ContentManager* sharedInstance = nil;
// NSLog(@"XXXXXXX Asking for icon for path %@ = %d",normalizedPath, [result intValue]);
if( result == nil ) {
// start the async call
[[RequestManager sharedInstance] askForIcon:normalizedPath isDirectory:isDir];
result = [NSNumber numberWithInt:0];
// Set 0 into the cache, meaning "don't have an icon, but already requested it"
[_fileNamesCache setObject:result forKey:normalizedPath];
// start the async call
[[RequestManager sharedInstance] askForIcon:normalizedPath isDirectory:isDir];
}
if ([result intValue] == 0) {
// Show the old state while we wait for the new one

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
0B13ECAE173C687400548DA1 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
0B13ECAF173C687900548DA1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BFC9ACB173C57E400CDD329 /* Security.framework */; };
5BB74A8719DBF9BB001BAAAC /* FinishedIconCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BB74A8619DBF9BB001BAAAC /* FinishedIconCache.m */; };
692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */; };
@ -21,13 +20,12 @@
8C99F6941622D145002D2135 /* IconCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C99F6931622D145002D2135 /* IconCache.m */; };
8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */; };
8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8D5B49A704867FD3000E48DA /* InfoPlist.strings */; };
C2B573831B1CD5AE00303B36 /* SyncClientProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
089C167EFE841241C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = "<absolute>"; };
0B13ECAC173C686900548DA1 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = "<group>"; };
0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = "<group>"; };
0B2BF60B176A43DB001246CD /* Finder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Finder.h; sourceTree = "<group>"; };
0BFC9ACB173C57E400CDD329 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
5BB74A8519DBF9BB001BAAAC /* FinishedIconCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FinishedIconCache.h; sourceTree = "<group>"; };
@ -50,6 +48,8 @@
8C99F6931622D145002D2135 /* IconCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IconCache.m; sourceTree = "<group>"; };
8D576316048677EA00EA77CD /* SyncStateFinder.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SyncStateFinder.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
8D576317048677EA00EA77CD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C2B573811B1CD5AE00303B36 /* SyncClientProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SyncClientProxy.h; sourceTree = "<group>"; };
C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncClientProxy.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -70,6 +70,7 @@
089C166AFE841209C02AAC07 /* SyncStateFinder */ = {
isa = PBXGroup;
children = (
C2B573801B1CD5AE00303B36 /* common */,
08FB77AFFE84173DC02AAC07 /* Source */,
089C167CFE841241C02AAC07 /* Resources */,
089C1671FE841209C02AAC07 /* External Frameworks and Libraries */,
@ -103,7 +104,6 @@
isa = PBXGroup;
children = (
0B2BF60A176A43DB001246CD /* Finder */,
0B08BAC21759627700C8351E /* GCDAsyncSocket */,
8C37DD99161593BD00016A95 /* FinderHook.h */,
8C37DD9A161593BD00016A95 /* FinderHook.m */,
692C18A316660C4600BF6A53 /* ContextMenuHandlers.h */,
@ -124,15 +124,6 @@
name = Source;
sourceTree = "<group>";
};
0B08BAC21759627700C8351E /* GCDAsyncSocket */ = {
isa = PBXGroup;
children = (
0B13ECAC173C686900548DA1 /* GCDAsyncSocket.h */,
0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */,
);
name = GCDAsyncSocket;
sourceTree = "<group>";
};
0B2BF60A176A43DB001246CD /* Finder */ = {
isa = PBXGroup;
children = (
@ -149,6 +140,16 @@
name = Products;
sourceTree = "<group>";
};
C2B573801B1CD5AE00303B36 /* common */ = {
isa = PBXGroup;
children = (
C2B573811B1CD5AE00303B36 /* SyncClientProxy.h */,
C2B573821B1CD5AE00303B36 /* SyncClientProxy.m */,
);
name = common;
path = ../common;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -213,10 +214,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0B13ECAE173C687400548DA1 /* GCDAsyncSocket.m in Sources */,
8C37DD9F161593BD00016A95 /* FinderHook.m in Sources */,
8C99F6941622D145002D2135 /* IconCache.m in Sources */,
69948B361636D50E0093B6CE /* ContentManager.m in Sources */,
C2B573831B1CD5AE00303B36 /* SyncClientProxy.m in Sources */,
6993878616494C000044E4DF /* RequestManager.m in Sources */,
5BB74A8719DBF9BB001BAAAC /* FinishedIconCache.m in Sources */,
692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */,

View file

@ -13,20 +13,18 @@
*/
#import <Foundation/Foundation.h>
#import "GCDAsyncSocket.h"
#import "RequestManager.h"
#import "SyncClientProxy.h"
@interface RequestManager : NSObject
@interface RequestManager : NSObject <SyncClientProxyDelegate>
{
GCDAsyncSocket* _socket;
SyncClientProxy *_syncClientProxy;
NSMutableArray* _requestQueue;
NSMutableDictionary* _registeredPathes;
NSMutableSet* _requestedPaths;
NSString *_shareMenuTitle;
BOOL _isConnected;
}
@property (nonatomic, retain) NSString* filterFolder;
@ -34,10 +32,8 @@
+ (RequestManager*)sharedInstance;
- (BOOL)isRegisteredPath:(NSString*)path isDirectory:(BOOL)isDir;
- (void)askOnSocket:(NSString*)path query:(NSString*)verb;
- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir;
- (void)menuItemClicked:(NSDictionary*)actionDictionary;
- (void)start;
- (NSString*) shareItemTitle;

View file

@ -26,16 +26,19 @@ static RequestManager* sharedInstance = nil;
{
if ((self = [super init]))
{
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
_isConnected = NO;
// For the sake of allowing both the legacy and the FinderSync extensions to work with the same
// client build, use the same server name including the Team ID even though we won't be signing the bundle.
NSString *serverName = @"9B5WD74GWJ.com.owncloud.desktopclient.socketApi";
_syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName];
_registeredPathes = [[NSMutableDictionary alloc] init];
_requestedPaths = [[NSMutableSet alloc] init];
_shareMenuTitle = nil;
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(start) userInfo:nil repeats:YES];
// The NSConnection will block until the distant object came back and this creates a loop hanging Finder.
// Start from a timer to have time to unwind the stack first.
[NSTimer scheduledTimerWithTimeInterval:0 target:_syncClientProxy selector:@selector(start) userInfo:nil repeats:NO];
}
return self;
@ -43,9 +46,7 @@ static RequestManager* sharedInstance = nil;
- (void)dealloc
{
[_socket setDelegate:nil delegateQueue:NULL];
[_socket disconnect];
[_socket release];
[_syncClientProxy release];
sharedInstance = nil;
@ -65,20 +66,6 @@ static RequestManager* sharedInstance = nil;
return sharedInstance;
}
- (void)askOnSocket:(NSString*)path query:(NSString*)verb
{
NSString *query = [NSString stringWithFormat:@"%@:%@\n", verb,path];
// NSLog(@"Query: %@", query);
NSData* data = [query dataUsingEncoding:NSUTF8StringEncoding];
[_socket writeData:data withTimeout:5 tag:4711];
NSData* stop = [@"\n" dataUsingEncoding:NSUTF8StringEncoding];
[_socket readDataToData:stop withTimeout:-1 tag:READ_TAG];
}
- (BOOL)isRegisteredPath:(NSString*)path isDirectory:(BOOL)isDir
{
// check if the file in question is underneath a registered directory
@ -104,118 +91,53 @@ static RequestManager* sharedInstance = nil;
- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir
{
NSString *verb = @"RETRIEVE_FILE_STATUS";
if( [self isRegisteredPath:path isDirectory:isDir] ) {
[_requestedPaths addObject:path];
if( _isConnected ) {
if(isDir) {
verb = @"RETRIEVE_FOLDER_STATUS";
}
[self askOnSocket:path query:verb];
} else {
[_requestQueue addObject:path];
[self start]; // try again to connect
}
[_syncClientProxy askForIcon:path isDirectory:isDir];
}
}
- (void)socket:(GCDAsyncSocket*)socket didReadData:(NSData*)data withTag:(long)tag
- (void)setResultForPath:(NSString*)path result:(NSString*)result
{
NSString *answer = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray *chunks = nil;
if (answer != nil && [answer length] > 0) {
// cut a trailing newline
answer = [answer substringToIndex:[answer length] - 1];
chunks = [answer componentsSeparatedByString: @":"];
}
ContentManager *contentman = [ContentManager sharedInstance];
if( chunks && [chunks count] > 0 && tag == READ_TAG ) {
// NSLog(@"READ from socket (%ld): <%@>", tag, answer);
if( [[chunks objectAtIndex:0] isEqualToString:@"STATUS"] ) {
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] ];
}
}
// The client will broadcast all changes, do not fill the cache for paths that Finder didn't ask for.
if ([_requestedPaths containsObject:path]) {
[contentman setResultForPath:path result:[chunks objectAtIndex:1]];
[[ContentManager sharedInstance] setResultForPath:path result:result];
}
} else if( [[chunks objectAtIndex:0] isEqualToString:@"UPDATE_VIEW"] ) {
NSString *path = [chunks objectAtIndex:1];
[_requestedPaths removeAllObjects];
[contentman reFetchFileNameCacheForPath:path];
} else if( [[chunks objectAtIndex:0 ] isEqualToString:@"REGISTER_PATH"] ) {
NSNumber *one = [NSNumber numberWithInt:1];
NSString *path = [chunks objectAtIndex:1];
// NSLog(@"Registering path: %@", path);
[_registeredPathes setObject:one forKey:path];
[contentman repaintAllWindows];
} else if( [[chunks objectAtIndex:0 ] isEqualToString:@"UNREGISTER_PATH"] ) {
NSString *path = [chunks objectAtIndex:1];
[_registeredPathes removeObjectForKey:path];
[contentman repaintAllWindows];
} else if( [[chunks objectAtIndex:0 ] isEqualToString:@"ICON_PATH"] ) {
NSString *path = [chunks objectAtIndex:1];
[[ContentManager sharedInstance] loadIconResourcePath:path];
} else if( [[chunks objectAtIndex:0 ] isEqualToString:@"SHARE_MENU_TITLE"] ) {
_shareMenuTitle = [[chunks objectAtIndex:1] copy];
// NSLog(@"Received shar menu title: %@", _shareMenuTitle);
} else {
NSLog(@"SyncState: Unknown command %@", [chunks objectAtIndex:0]);
}
} else if (tag != READ_TAG) {
NSLog(@"SyncState: Received unknown tag %ld <%@>", tag, answer);
}
// Read on and on
NSData* stop = [@"\n" dataUsingEncoding:NSUTF8StringEncoding];
[_socket readDataToData:stop withTimeout:-1 tag:READ_TAG];
}
- (NSTimeInterval)socket:(GCDAsyncSocket*)socket shouldTimeoutReadWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length
- (void)reFetchFileNameCacheForPath:(NSString*)path
{
// Called if a read operation has reached its timeout without completing.
return 0.0;
[_requestedPaths removeAllObjects];
[[ContentManager sharedInstance] reFetchFileNameCacheForPath:path];
}
-(void)socket:(GCDAsyncSocket*)socket didConnectToUrl:(NSURL *)url {
// NSLog( @"Connected to sync client successfully on %@", url);
_isConnected = YES;
[self askOnSocket:@"" query:@"SHARE_MENU_TITLE"];
if( [_requestQueue count] > 0 ) {
// NSLog( @"We have to empty the queue");
for( NSString *path in _requestQueue ) {
[self askOnSocket:path query:@"RETRIEVE_FILE_STATUS"];
}
[_requestQueue removeAllObjects];
- (void)registerPath:(NSString*)path
{
NSNumber *one = [NSNumber numberWithInt:1];
[_registeredPathes setObject:one forKey:path];
[[ContentManager sharedInstance] repaintAllWindows];
}
ContentManager *contentman = [ContentManager sharedInstance];
[contentman clearFileNameCache];
[contentman repaintAllWindows];
// Read for the UPDATE_VIEW requests
NSData* stop = [@"\n" dataUsingEncoding:NSUTF8StringEncoding];
[_socket readDataToData:stop withTimeout:-1 tag:READ_TAG];
- (void)unregisterPath:(NSString*)path
{
[_registeredPathes removeObjectForKey:path];
[[ContentManager sharedInstance] repaintAllWindows];
}
- (void)socketDidDisconnect:(GCDAsyncSocket*)socket withError:(NSError*)err
- (void)setShareMenuTitle:(NSString*)title
{
_shareMenuTitle = title;
}
- (void)loadIconResourcePath:(NSString*)path
{
[[ContentManager sharedInstance] loadIconResourcePath:path];
}
- (void)connectionDidDie
{
// NSLog(@"Socket DISconnected! %@", [err localizedDescription]);
_isConnected = NO;
// clear the registered pathes.
[_registeredPathes release];
_registeredPathes = [[NSMutableDictionary alloc] init];
@ -227,92 +149,18 @@ static RequestManager* sharedInstance = nil;
[contentman repaintAllWindows];
}
- (NSDate*)fileDate:(NSString*)fn
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = 0;
NSDictionary *oneAttributes = [fileManager attributesOfItemAtPath:fn error:&error];
if (!error) {
return [oneAttributes valueForKey:NSFileModificationDate];
}
return nil;
}
- (NSString*) getNewerFileOne:(NSString*)one two:(NSString*)two
{
if (!one) {
return two;
}
NSDate *oneDate = [self fileDate:one];
NSDate *twoDate = [self fileDate:two];
if (oneDate && twoDate) {
if ([oneDate compare:twoDate] == NSOrderedDescending) {
return one;
} else if ([oneDate compare:twoDate] == NSOrderedAscending) {
return two;
} else {
return two;
}
}
return one;
}
- (void)start
{
if (!_isConnected && ![_socket isConnected])
{
NSError *err = nil;
NSURL *url = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
if ([paths count])
{
// e.g file:///Users/guruz/Library/Caches/SyncStateHelper/ownCloud.socket
NSString *syncStateHelperDir = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"SyncStateHelper"];
NSError *pnsError = NULL;
NSArray *paths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:syncStateHelperDir error:&pnsError];
if (!pnsError && paths && [paths count] > 0) {
NSString *currentLatestPath = nil;
if (paths.count > 1) {
// NSLog(@"Possible paths: %@", paths);
}
for (int i = 0; i < paths.count; i++) {
NSString *currentPath = [syncStateHelperDir stringByAppendingPathComponent:[paths objectAtIndex:i]];
if (![currentPath hasSuffix:@".socket"]) {
continue;
}
currentLatestPath = [self getNewerFileOne:currentLatestPath two:currentPath];
}
// FIXME Instead of connecting to the newest socket we could go multi-socket to support multiple instances
if (currentLatestPath) {
url = [NSURL fileURLWithPath:currentLatestPath];
}
}
}
if (url) {
// NSLog(@"Connect Socket to %@", url);
[_socket connectToUrl:url withTimeout:1 error:&err];
}
}
}
- (void)menuItemClicked:(NSDictionary*)actionDictionary
{
// NSLog(@"RequestManager menuItemClicked %@", actionDictionary);
NSArray *filePaths = [actionDictionary valueForKey:@"files"];
for (int i = 0; i < filePaths.count; i++) {
[self askOnSocket:[filePaths objectAtIndex:i] query:@"SHARE"];
[_syncClientProxy askOnSocket:[filePaths objectAtIndex:i] query:@"SHARE"];
}
}
- (NSString*) shareItemTitle
{
if (_socket && _socket.isConnected && _shareMenuTitle) {
return _shareMenuTitle;
}
return nil;
}
@end

View file

@ -0,0 +1,44 @@
/*
* 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)setShareMenuTitle:(NSString*)title;
- (void)loadIconResourcePath:(NSString*)path;
- (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

View file

@ -0,0 +1,151 @@
/*
* 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 querrying
[self askOnSocket:@"" query:@"SHARE_MENU_TITLE"];
}
- (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
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:@"ICON_PATH"] ) {
// FIXME: Should also go away once we move icons into the bundle
NSString *path = [chunks objectAtIndex:1];
[_delegate loadIconResourcePath:path];
} else if( [[chunks objectAtIndex:0 ] isEqualToString:@"SHARE_MENU_TITLE"] ) {
[_delegate setShareMenuTitle:[chunks objectAtIndex:1]];
} 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

View file

@ -87,6 +87,7 @@ set(updater_SRCS
IF( APPLE )
list(APPEND client_SRCS cocoainitializer_mac.mm)
list(APPEND client_SRCS settingsdialogmac.cpp)
list(APPEND client_SRCS socketapisocket_mac.mm)
list(APPEND client_SRCS systray.mm)
if(SPARKLE_FOUND)

View file

@ -84,13 +84,11 @@ SocketApi::SocketApi(QObject* parent)
// See issue #2388
// + Theme::instance()->appName();
} else if (Utility::isMac()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
// Always using Qt5 on OS X
QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
socketPath = runtimeDir + "/SyncStateHelper/" + Theme::instance()->appName() + ".socket";
// We use the generic SyncStateHelper name on OS X since the different branded clients
// should unfortunately not mention that they are ownCloud :-)
#endif
// This much match the code signing Team setting of the extension
// FIXME: Hardcoded for now, but if we want to allow builds to be
// signed by third party Apple Developer accounts, we'll have to
// allow changing this through the build system.
socketPath = "9B5WD74GWJ." APPLICATION_REV_DOMAIN ".socketApi";
} else if( Utility::isLinux() || Utility::isBSD() ) {
QString runtimeDir;
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
@ -108,7 +106,7 @@ SocketApi::SocketApi(QObject* parent)
DEBUG << "An unexpected system detected";
}
QLocalServer::removeServer(socketPath);
SocketApiServer::removeServer(socketPath);
QFileInfo info(socketPath);
if (!info.dir().exists()) {
bool result = info.dir().mkpath(".");
@ -167,7 +165,7 @@ void SocketApi::slotReadExcludes()
void SocketApi::slotNewConnection()
{
QLocalSocket* socket = _localServer.nextPendingConnection();
QIODevice* socket = _localServer.nextPendingConnection();
if( ! socket ) {
return;
@ -199,7 +197,7 @@ void SocketApi::onLostConnection()
{
DEBUG << "Lost connection " << sender();
QLocalSocket* socket = qobject_cast<QLocalSocket*>(sender());
QIODevice* socket = qobject_cast<QIODevice*>(sender());
_listeners.removeAll(socket);
socket->deleteLater();
}
@ -207,7 +205,7 @@ void SocketApi::onLostConnection()
void SocketApi::slotReadSocket()
{
QLocalSocket* socket = qobject_cast<QLocalSocket*>(sender());
QIODevice* socket = qobject_cast<QIODevice*>(sender());
Q_ASSERT(socket);
while(socket->canReadLine()) {
@ -215,12 +213,12 @@ void SocketApi::slotReadSocket()
QString command = line.split(":").first();
QString function = QString(QLatin1String("command_")).append(command);
QString functionWithArguments = function + QLatin1String("(QString,QLocalSocket*)");
QString functionWithArguments = function + QLatin1String("(QString,QIODevice*)");
int indexOfMethod = this->metaObject()->indexOfMethod(functionWithArguments.toAscii());
QString argument = line.remove(0, command.length()+1).trimmed();
if(indexOfMethod != -1) {
QMetaObject::invokeMethod(this, function.toAscii(), Q_ARG(QString, argument), Q_ARG(QLocalSocket*, socket));
QMetaObject::invokeMethod(this, function.toAscii(), Q_ARG(QString, argument), Q_ARG(QIODevice*, socket));
} else {
DEBUG << "The command is not supported by this version of the client:" << command << "with argument:" << argument;
}
@ -232,7 +230,7 @@ void SocketApi::slotRegisterPath( const QString& alias )
Folder *f = FolderMan::instance()->folder(alias);
if (f) {
QString message = buildRegisterPathMessage(f->path());
foreach(QLocalSocket *socket, _listeners) {
foreach(QIODevice *socket, _listeners) {
sendMessage(socket, message);
}
}
@ -336,7 +334,7 @@ void SocketApi::slotSyncItemDiscovered(const QString &folder, const SyncFileItem
void SocketApi::sendMessage(QLocalSocket *socket, const QString& message, bool doWait)
void SocketApi::sendMessage(QIODevice *socket, const QString& message, bool doWait)
{
DEBUG << "Sending message: " << message;
QString localMessage = message;
@ -369,14 +367,12 @@ void SocketApi::broadcastMessage( const QString& verb, const QString& path, cons
msg.append(QDir::toNativeSeparators(fi.absoluteFilePath()));
}
// sendMessage already has a debug output
//DEBUG << "Broadcasting to" << _listeners.count() << "listeners: " << msg;
foreach(QLocalSocket *socket, _listeners) {
foreach(QIODevice *socket, _listeners) {
sendMessage(socket, msg, doWait);
}
}
void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, QLocalSocket* socket)
void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, QIODevice* socket)
{
// This command is the same as RETRIEVE_FILE_STATUS
@ -384,7 +380,7 @@ void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, QLocalSo
command_RETRIEVE_FILE_STATUS(argument, socket);
}
void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QLocalSocket* socket)
void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QIODevice* socket)
{
if( !socket ) {
qDebug() << "No valid socket object.";
@ -412,7 +408,7 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QLocalSock
sendMessage(socket, message);
}
void SocketApi::command_SHARE(const QString& localFile, QLocalSocket* socket)
void SocketApi::command_SHARE(const QString& localFile, QIODevice* socket)
{
if (!socket) {
qDebug() << Q_FUNC_INFO << "No valid socket object.";
@ -450,12 +446,12 @@ void SocketApi::command_SHARE(const QString& localFile, QLocalSocket* socket)
}
}
void SocketApi::command_VERSION(const QString&, QLocalSocket* socket)
void SocketApi::command_VERSION(const QString&, QIODevice* socket)
{
sendMessage(socket, QLatin1String("VERSION:" MIRALL_VERSION_STRING ":" MIRALL_SOCKET_API_VERSION));
}
void SocketApi::command_SHARE_MENU_TITLE(const QString &, QLocalSocket* socket)
void SocketApi::command_SHARE_MENU_TITLE(const QString &, QIODevice* socket)
{
sendMessage(socket, QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI()));
}

View file

@ -20,17 +20,17 @@ extern "C" {
#include <std/c_string.h>
}
#include <sqlite3.h>
#include <QWeakPointer>
#include <QTcpSocket>
#include <QTcpServer>
#include <QLocalServer>
#include "syncfileitem.h"
#include "syncjournalfilerecord.h"
#include "ownsql.h"
#if defined(Q_OS_MAC)
#include "socketapisocket_mac.h"
#else
#include <QLocalServer>
typedef QLocalServer SocketApiServer;
#endif
class QUrl;
class QLocalSocket;
class QStringList;
@ -70,20 +70,20 @@ private:
SyncJournalFileRecord dbFileRecord_capi( Folder *folder, QString fileName );
SqlQuery *getSqlQuery( Folder *folder );
void sendMessage(QLocalSocket* socket, const QString& message, bool doWait = false);
void sendMessage(QIODevice* socket, const QString& message, bool doWait = false);
void broadcastMessage(const QString& verb, const QString &path, const QString &status = QString::null, bool doWait = false);
Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString& argument, QLocalSocket* socket);
Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString& argument, QLocalSocket* socket);
Q_INVOKABLE void command_SHARE(const QString& localFile, QLocalSocket* socket);
Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString& argument, QIODevice* socket);
Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString& argument, QIODevice* socket);
Q_INVOKABLE void command_SHARE(const QString& localFile, QIODevice* socket);
Q_INVOKABLE void command_VERSION(const QString& argument, QLocalSocket* socket);
Q_INVOKABLE void command_VERSION(const QString& argument, QIODevice* socket);
Q_INVOKABLE void command_SHARE_MENU_TITLE(const QString& argument, QLocalSocket* socket);
Q_INVOKABLE void command_SHARE_MENU_TITLE(const QString& argument, QIODevice* socket);
QString buildRegisterPathMessage(const QString& path);
QList<QLocalSocket*> _listeners;
QLocalServer _localServer;
QList<QIODevice*> _listeners;
SocketApiServer _localServer;
c_strlist_t *_excludes;
QHash<Folder*, QSharedPointer<SqlQuery>> _dbQueries;
QHash<Folder*, QSharedPointer<SqlDatabase>> _openDbs;

View file

@ -0,0 +1,69 @@
/*
* 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

View file

@ -0,0 +1,225 @@
/*
* 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();
NSDistantObject <ChannelProtocol> *remoteEnd;
LocalEnd *localEnd;
QByteArray inBuffer;
};
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)
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)
{
@try {
Q_D(SocketApiSocket);
// 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.
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()
{
[remoteEnd release];
// The DO vended localEnd might still be referenced by the connection
localEnd.wrapper = nil;
[localEnd 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];
}