nextcloud-desktop/shell_integration/MacOSX/OwnCloudFinder/RequestManager.m

318 lines
9.2 KiB
Objective-C

/**
* Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library 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 Lesser General Public License for more
* details.
*/
#import "ContentManager.h"
#import "IconCache.h"
#import "RequestManager.h"
#define READ_TAG 2422
static RequestManager* sharedInstance = nil;
@implementation RequestManager
- (id)init
{
if ((self = [super init]))
{
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
_isConnected = NO;
_registeredPathes = [[NSMutableDictionary alloc] init];
_requestedPaths = [[NSMutableSet alloc] init];
_shareMenuTitle = nil;
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(start) userInfo:nil repeats:YES];
}
return self;
}
- (void)dealloc
{
[_socket setDelegate:nil delegateQueue:NULL];
[_socket disconnect];
[_socket release];
sharedInstance = nil;
[super dealloc];
}
+ (RequestManager*)sharedInstance
{
@synchronized(self)
{
if (sharedInstance == nil)
{
sharedInstance = [[self alloc] init];
}
}
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
NSArray *regPathes = [_registeredPathes allKeys];
BOOL registered = NO;
NSString* checkPath = [NSString stringWithString:path];
if (isDir && ![checkPath hasSuffix:@"/"]) {
// append a slash
checkPath = [path stringByAppendingString:@"/"];
}
for( NSString *regPath in regPathes ) {
if( [checkPath hasPrefix:regPath]) {
// the path was registered
registered = YES;
break;
}
}
return registered;
}
- (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
}
}
}
- (void)socket:(GCDAsyncSocket*)socket didReadData:(NSData*)data withTag:(long)tag
{
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]];
}
} 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
{
// Called if a read operation has reached its timeout without completing.
return 0.0;
}
-(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];
}
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)socketDidDisconnect:(GCDAsyncSocket*)socket withError:(NSError*)err
{
// NSLog(@"Socket DISconnected! %@", [err localizedDescription]);
_isConnected = NO;
// clear the registered pathes.
[_registeredPathes release];
_registeredPathes = [[NSMutableDictionary alloc] init];
[_requestedPaths removeAllObjects];
// clear the caches in conent manager
ContentManager *contentman = [ContentManager sharedInstance];
[contentman clearFileNameCache];
[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"];
}
}
- (NSString*) shareItemTitle
{
if (_socket && _socket.isConnected && _shareMenuTitle) {
return _shareMenuTitle;
}
return nil;
}
@end