// // P4Connection.m // MBMenuExtra // // Created by Michael Bishop on 1/7/10. // Copyright 2010 Perforce Software. All rights reserved. // #import "P4RawConnection.h" #import "P4Port.h" #import "P4ConnectionPool.h" #import "P4ConnectionPool_p.h" #import "P4ErrorCodes.h" #import "P4Keychain.h" #import "P4TaggedDataInflaterTransformer.h" #import "P4Response.h" #import "P4Response_p.h" NSString * const kP4ConnectionUserContextKey = @"P4USER"; NSString * const kP4ConnectionEncodingContextKey = @"P4CHARSET"; NSString * const kP4ConnectionClientContextKey = @"P4CLIENT"; NSString * const kP4ConnectionPasswordContextKey = @"P4PASSWD"; NSString * const kP4ConnectionCWDContextKey = @"P4CWD"; static NSString * _defaultP4Port; static NSString * _defaultUser; @interface P4RawConnection () @property (readwrite, retain) NSError * error; @property (readwrite, retain) NSMutableArray * results; @property (readwrite, retain) NSString * output; @property (readwrite, retain) NSString * input; @property (readwrite, copy) UpdateBlock updateBlock; @end static void SimulateNetworkDelay(NSTimeInterval time) { if ( time > 0.0 ) usleep((useconds_t)(time * 1000000.0)); } @implementation P4RawConnection @synthesize portString = _p4port , results = _results , error = _error , output = _output , input = _input , updateBlock , networkDelay ; +(void)initialize { P4ClientApi * api = [[P4ClientApi alloc] init]; _defaultP4Port = [api.p4port retain]; _defaultUser = [api.user retain]; [api release]; } +(NSString*)defaultP4Port { return _defaultP4Port; } +(NSString*)defaultUsername { return _defaultUser; } -(NSInteger)operationCount { return [_queue operationCount]; } -(id)initWithPortString:(NSString*)p4port pool:(P4ConnectionPool*)pool { if ( (self = [super init]) == nil ) return nil; _api = [[P4ClientApi alloc] init]; // Masquerade as P4V _api.programIdentifier = @"P4V/MACOSX104U/2010.1/256349"; _p4port = [p4port copy]; _pool = pool; _queue = [[NSOperationQueue alloc] init]; [_queue setName:p4port]; // only one operation at a time on the api [_queue setMaxConcurrentOperationCount:1]; _results = [[NSMutableArray alloc] init]; _updateResultQueue = [[NSMutableArray alloc] init]; return self; } -(void)dealloc { [_queue release]; [_p4port release]; [_api release]; [_results release]; [_updateResultQueue release]; [_input release]; [_output release]; [_error release]; [super dealloc]; } -(NSString*)description { // return [NSString stringWithFormat:@"(p4port:%@ queueCount:%@)", self.p4port, [_queue operationCount]]; return [NSString stringWithFormat:@"(p4port:%@)", self.portString]; } #pragma mark PRIVATE -(void)P4_clearResults { [_results removeAllObjects]; self.error = nil; self.output = nil; } -(void)P4_removeIfLastOperation { if ( [_queue operationCount] == 1 ) [_pool removeConnection:self]; } -(void)P4_setupContext:(NSDictionary*)context { _api.user = [context objectForKey:kP4ConnectionUserContextKey]; _api.client = [context objectForKey:kP4ConnectionClientContextKey]; _api.charset = [context objectForKey:kP4ConnectionEncodingContextKey]; _api.currentWorkingDirectory = [context objectForKey:kP4ConnectionCWDContextKey]; _api.password = [context objectForKey:kP4ConnectionPasswordContextKey]; } -(void)P4_postConnectionNotificationWithResponse:(P4Response*)response arguments:(NSArray*)arguments context:(NSDictionary*)context { NSDictionary * userInfo = [NSDictionary dictionaryWithObjectsAndKeys: response, kP4ServerConnectionReceivedErrorNotificationResponseKey ,arguments, kP4ServerConnectionReceivedErrorNotificationArgumentsKey ,context, kP4ServerConnectionReceivedErrorNotificationContextKey ,nil]; [[NSNotificationCenter defaultCenter] postNotificationName:P4ServerConnectionReceivedErrorNotification object:self userInfo:userInfo]; } #pragma mark PUBLIC #pragma mark Asynchronous Calls -(BOOL)runArguments:(NSArray*)arguments withContext:(NSDictionary*)context updateBlock:(UpdateBlock)update completionBlock:(void(^)(P4Response*))completion { return [self runArguments:arguments withContent:nil context:context updateBlock:update completionBlock:completion]; } -(BOOL)runArguments:(NSArray*)arguments withContent:(NSString*)content context:(NSDictionary*)context updateBlock:(UpdateBlock)update completionBlock:(void(^)(P4Response*))completion { self.input = content; [_queue addOperationWithBlock:^{ // Run the operation self.updateBlock = update; P4Response * response = [self runArguments:(NSArray*)arguments withContext:(NSDictionary*)context content:(NSString*)content]; self.updateBlock = nil; if ( update ) { dispatch_sync( dispatch_get_main_queue(), ^{ NSArray * results = nil; @synchronized (_updateResultQueue) { results = [NSArray arrayWithArray:_updateResultQueue]; [_updateResultQueue removeAllObjects]; } for (NSDictionary * d in results) update( d ); }); } dispatch_async( dispatch_get_main_queue(), ^{ self.input = nil; // If there is an error, pass the error with the current // operation to an error handler. If the error handler can // fix it, the operation is re-added to the queue. // If not, the whole thing is dropped. completion( response ); // if (response.error) // LOG_DEBUG( @"%@ returned a response %@", arguments, response ); [_pool connectionFinished:self]; [self P4_removeIfLastOperation]; }); }]; return YES; } #pragma mark Synchronous Calls -(P4Response*)runArguments:(NSArray*)arguments withContext:(NSDictionary*)context { return [self runArguments:arguments withContext:context content:nil]; }; -(P4Response*)runArguments:(NSArray*)arguments withContext:(NSDictionary*)context content:(NSString*)content { P4Response * response = nil; // If there is a connection error, just immediately report the error // and exit. If there is only one operation left, it's our operation // and we can remove ourself from the pool following. _api.userInfo = self.updateBlock; if ( !_api.connected && IsConnectionError(_error) ) { LOG_ERROR(@"%@: running %@. Previous connection error: %@", self, [arguments objectAtIndex:0], _error); P4Response * response = [P4Response responseWithError:_error results:nil]; return response; } // CONNECT if ( !_api.connected ) { NSError * error = nil; if (![_api connectToPort:self.portString withProtocol:nil error:&error]) { response = [P4Response responseWithError:error results:nil]; [self P4_postConnectionNotificationWithResponse:response arguments:arguments context:context]; LOG_ERROR(@"%@: failed connection while running %@. error: %@", self, [arguments objectAtIndex:0], error); return response; } } [self P4_setupContext:context]; // RUN COMMAND NSRange remainingArgs; remainingArgs.location = 1; remainingArgs.length = [arguments count] - 1; [self P4_clearResults]; [_api runCommand:[arguments objectAtIndex:0] withArguments:[arguments subarrayWithRange:remainingArgs] delegate:self]; response = [P4Response responseWithError:_error results:_results]; [self P4_postConnectionNotificationWithResponse:response arguments:arguments context:context]; return response; } #pragma mark DELEGATE METHODS -(void)clientApi:(P4ClientApi*)api didReceiveError:(NSError*)error { SimulateNetworkDelay(self.networkDelay); self.error = error; } -(void)clientApi:(P4ClientApi*)api didReceiveTaggedResponse:(NSDictionary*)rawResponse { SimulateNetworkDelay(self.networkDelay); NSDictionary * response = [[P4TaggedDataInflaterTransformer sharedInstance] transformedValue:rawResponse]; // Filter results with the update block [_results addObject:response]; UpdateBlock uBlock = api.userInfo; if ( !uBlock ) return; BOOL runFilterCleanup = NO; @synchronized (_updateResultQueue) { if ( [_updateResultQueue count] == 0 ) runFilterCleanup = YES; [_updateResultQueue addObject:response]; } if ( runFilterCleanup ) dispatch_async( dispatch_get_main_queue(), ^{ NSArray * results = nil; @synchronized (_updateResultQueue) { results = [NSArray arrayWithArray:_updateResultQueue]; [_updateResultQueue removeAllObjects]; } // LOG_DEBUG( @"Processing %d results", [results count] ); for (NSDictionary * d in results) uBlock( d ); }); } -(void)clientApi:(P4ClientApi*)api didRequestInputAsText:(NSString**)input { *input = self.input; } -(void)clientApi:(P4ClientApi*)api didPromptUserWithText:(NSString*)text response:(NSString**)response supressingEcho:(BOOL)supressingEcho { *response = self.input; } -(void)clientApi:(P4ClientApi*)api didReceiveSimpleServerMessage:(NSString*)response level:(char)level { self.output = response; } - (BOOL)writeResultsToFileNamed:(NSString*)name fullPath:(NSString**)fullPath { #ifdef WRITE_RESULTS NSString * path = [NSString stringWithFormat:@"/tmp/P4CommandRunnerResults/%@/", self.api.p4port]; if ( ![[NSFileManager defaultManager] fileExistsAtPath:path] ) { NSError * error; if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error]) { LOG_ERROR( @"Could not create directory for P4CommandRunner output at %@. Error: %@", path, error ); return NO; } } path = [path stringByAppendingString:name]; if (fullPath) *fullPath = path; return [self.results writeToFile:path atomically:YES]; #else return YES; #endif } @end