/******************************************************************************* Copyright (c) 2001-2009, Perforce Software, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /*===================================================================\ | Name : P4ClientApi.mm | | Author : Michael Bishop <mbishop@perforce.com> | | Description: Objective-C wrapper for the Perforce API. \===================================================================*/ #import "P4ClientApi.h" #import "P4ClientApiPriv.h" #import "P4TypeConversions.hpp" #include "spec.h" #import "i18napi.h" #import "p4tags.h" #import "spec.h" /*===================================================================\ | | | PUBLIC STRING CONSTANTS | | | \===================================================================*/ static NSString * StringConstant( const char * ); NSString * const P4ErrorDomain = @"com.perforce.p4.ErrorDomain"; NSString * const P4SubsystemErrorKey = @"P4ErrorSubsystem"; NSString * const P4SubCodeErrorKey = @"P4ErrorSubCode"; NSString * const P4SeverityErrorKey = @"P4ErrorSeverity"; NSString * const P4GenericErrorKey = @"P4ErrorGeneric"; NSString * const P4ProtocolVersion = StringConstant(P4Tag::v_api); /*===================================================================\ | | | INTERNAL PRIVATE METHODS AND PROPERTIES | | | \===================================================================*/ @interface P4ClientApi () -(void)P4_setArguments:(NSArray*)args forClientApi:(ClientApi*)api; -(void)P4_observeCallbackThread; -(void)P4_unobserveCallbackThread; -(void)P4_threadWillExit:(NSNotification*)notification; @property(readonly) ClientApi * api; // Make the publicly read-only properties privately writable @property(readwrite, copy) NSString * p4port; @property(readwrite, copy) NSDictionary * requestedProtocol; @property(readwrite) BOOL connected; @property(readwrite, copy) NSString * currentCommand; @property(readwrite, copy) NSArray * currentArguments; @end /*===================================================================\ | | | IMPLEMENTATION | | | \===================================================================*/ @implementation P4ClientApi @synthesize connected = _connected , currentCommand = _currentCommand , currentArguments = _currentArgs , userInfo = _userInfo , callbackThread = _callbackThread , ticketFilePath = _ticketFilePath , programIdentifier = _programIdentifier , version = _version , requestedProtocol = _requestedProtocol , prefersTaggedOutput = _prefersTaggedOutput , rowScanningLimit = _rowScanningLimit , tableLockingTimeLimit = _tableLockingTimeLimit , returnedResultsLimit = _returnedResultsLimit ; -(id)init { if ( (self = [super init]) == nil ) return self; _clientApi = new ClientApi(); _prefersTaggedOutput = YES; return self; } -(void)dealloc { // we might be dealloced in response to delegates receiving // a callback so we need to zeroout all the data as well as release it // because there might be code remaining on the stack that might want to // send messages to these variables (like release) [_userInfo release]; _userInfo = nil; [_currentCommand release]; _currentCommand = nil; [_currentArgs release]; _currentArgs = nil; [_ticketFilePath release]; _ticketFilePath = nil; [_programIdentifier release]; _programIdentifier = nil; [_version release]; _version = nil; [_requestedProtocol release]; _requestedProtocol = nil; [self disconnect:nil]; delete self.api; [super dealloc]; } -(ClientApi*)api { return (ClientApi*)_clientApi; } -(ClientUserWrapper*)currentClientUser { return (ClientUserWrapper*)_currentClientUser; } #pragma mark making/breaking the connection /*-------------------------------------------------------------------\ | | | Connecting/Disconnecting | | | \-------------------------------------------------------------------*/ -(BOOL)connectToPort:(NSString*)port withProtocol:(NSDictionary*)protocol error:(NSError **)error { Error e; @synchronized (self) { self.p4port = port; self.requestedProtocol = protocol; self.api->Init(&e); if ( !e.Test() ) [self setConnected: YES]; } if ( !e.Test() ) return YES; if (error) *error = [NSError errorWithP4Error:e]; return NO; } -(void)connectToPort:(NSString*)port withProtocol:(NSDictionary*)protocol delegate:(id<P4ClientApiConnectionDelegate>)delegate { NSError * error; // we can only call back in another thread if we can use NSObject methods BOOL delegateIsAnNSObject = [delegate isKindOfClass:[NSObject class]]; if ( [self connectToPort:port withProtocol:protocol error:&error] == YES ) { if (!_callbackThread || !delegateIsAnNSObject ) { [delegate clientApiDidConnect:self]; return; } [(NSObject *)delegate performSelector:@selector(clientApiDidConnect:) onThread:_callbackThread withObject:self waitUntilDone:YES]; return; } if (!_callbackThread || !delegateIsAnNSObject) { [delegate clientApi:self didFailToConnectWithError:error]; return; } SEL sel = @selector(clientApi:didFailToConnectWithError:); NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:[(NSObject *)delegate methodSignatureForSelector:sel]]; [invocation setTarget:delegate]; [invocation setSelector:sel]; [invocation setArgument:&self atIndex:2]; [invocation setArgument:&error atIndex:3]; [invocation performSelector:@selector(invoke) onThread:_callbackThread withObject:nil waitUntilDone:YES]; } -(NSInvocation*)invocationForConnectToPort:(NSString*)port withProtocol:(NSDictionary*)protocol delegate:(id<P4ClientApiConnectionDelegate>)delegate { const SEL runCommandSelector = @selector(connectToPort:withProtocol:delegate:); NSInvocation * invocation = [NSInvocation invocationWithMethodSignature: [self methodSignatureForSelector:runCommandSelector]]; [invocation setSelector:runCommandSelector]; [invocation setTarget:self]; if ( port ) [invocation setArgument:&port atIndex:2]; if ( protocol ) [invocation setArgument:&protocol atIndex:3]; if ( delegate ) [invocation setArgument:&delegate atIndex:4]; return invocation; } -(BOOL)disconnect:(NSError **)error { Error e; @synchronized (self) { if ( !self.connected ) return YES; self.api->Final(&e); [self setConnected:NO]; } if ( !e.Test() ) return YES; if (error) *error = [NSError errorWithP4Error:e]; return NO; } -(void)disconnectWithDelegate:(id<P4ClientApiConnectionDelegate>)delegate { NSError * error; [self disconnect:&error]; // we can only call back in another thread if we can use NSObject methods BOOL delegateIsAnNSObject = [delegate isKindOfClass:[NSObject class]]; if ( !_callbackThread || !delegateIsAnNSObject ) [delegate clientApi:self didDisconnectWithError:error]; SEL sel = @selector(clientApi:didDisconnectWithError:); NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:[(NSObject*)delegate methodSignatureForSelector:sel]]; [invocation setTarget:delegate]; [invocation setSelector:sel]; [invocation setArgument:&self atIndex:2]; [invocation setArgument:&error atIndex:3]; [invocation performSelector:@selector(invoke) onThread:_callbackThread withObject:nil waitUntilDone:YES]; } -(NSInvocation*)invocationForDisconnectWithDelegate:(id<P4ClientApiConnectionDelegate>)delegate { SEL selector = @selector(disconnectWithDelegate:); NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]]; [invocation setTarget:self]; [invocation setSelector:selector]; if ( delegate ) [invocation setArgument:&delegate atIndex:2]; return invocation; } #pragma mark executing commands /*===================================================================\ | | | Executing Commands | | | \===================================================================*/ -(void)P4_setArguments:(NSArray*)args forClientApi:(ClientApi*)api { if (args == nil) return; char ** a = (char**)malloc( [args count] * sizeof(char *) ); int i = 0; for (NSString * arg in args) a[i++] = const_cast<char *>([arg UTF8String]); api->SetArgv( i, a ); free( a ); } -(void)runCommand:(NSString *)function withArguments:(NSArray*)args commandVariables:(NSDictionary*)variables delegate:(id<P4ClientApiCommandDelegate>)delegate { // set the per-command variables if ( self.programIdentifier ) self.api->SetProg([self.programIdentifier UTF8String]); if ( self.version ) self.api->SetVersion([self.version UTF8String]); NSMutableDictionary * allVariables = [NSMutableDictionary dictionaryWithDictionary:variables]; if ( self.prefersTaggedOutput ) [allVariables setObject:[NSNull null] forKey:[NSString stringWithUTF8String:P4Tag::v_tag]]; if ( self.rowScanningLimit > 0 ) [allVariables setObject:[NSNumber numberWithInt:self.rowScanningLimit] forKey:[NSString stringWithUTF8String:P4Tag::v_maxScanRows]]; if ( self.tableLockingTimeLimit > 0 ) [allVariables setObject:[NSNumber numberWithInt:self.tableLockingTimeLimit] forKey:[NSString stringWithUTF8String:P4Tag::v_maxLockTime]]; if ( self.returnedResultsLimit > 0 ) [allVariables setObject:[NSNumber numberWithInt:self.returnedResultsLimit] forKey:[NSString stringWithUTF8String:P4Tag::v_maxResults]]; for ( NSString * variableName in [allVariables allKeys] ) { const char * utf8VariableName = [variableName UTF8String]; id value = [allVariables objectForKey:variableName]; if ( [value isKindOfClass:[NSNull class]] ) self.api->SetVar( utf8VariableName ); else if ( [value isKindOfClass:[NSNumber class]] ) self.api->SetVar( utf8VariableName, [value intValue] ); else if ( [value isKindOfClass:[NSString class]] ) self.api->SetVar( utf8VariableName, [value UTF8String] ); else NSLog(@"Cannot set value of type %@ for variable %@", [value class], variableName); } self.currentCommand = function; self.currentArguments = args; [self P4_setArguments:args forClientApi:self.api]; ClientUserWrapper * wrapper = new ClientUserWrapper(self, delegate);; _currentClientUser = wrapper; self.api->Run([function UTF8String], wrapper ); _currentClientUser = NULL; delete wrapper; self.currentCommand = nil; self.currentArguments = nil; } -(void)runCommand:(NSString *)function withArguments:(NSArray*)args delegate:(id<P4ClientApiCommandDelegate>)delegate { NSMutableDictionary * commandVariables = [NSMutableDictionary dictionary]; if ( self.prefersTaggedOutput ) [commandVariables setObject:@"" forKey:[NSString stringWithUTF8String:P4Tag::v_tag]]; if ( self.rowScanningLimit > 0 ) [commandVariables setObject:[NSNumber numberWithInt:self.rowScanningLimit] forKey:[NSString stringWithUTF8String:P4Tag::v_maxScanRows]]; if ( self.tableLockingTimeLimit > 0 ) [commandVariables setObject:[NSNumber numberWithInt:self.tableLockingTimeLimit] forKey:[NSString stringWithUTF8String:P4Tag::v_maxLockTime]]; if ( self.returnedResultsLimit > 0 ) [commandVariables setObject:[NSNumber numberWithInt:self.returnedResultsLimit] forKey:[NSString stringWithUTF8String:P4Tag::v_maxResults]]; [self runCommand:function withArguments:args commandVariables:commandVariables delegate:delegate]; } -(NSInvocation*)invocationForRunCommand:(NSString *)command withArguments:(NSArray*)arguments delegate:(id<P4ClientApiCommandDelegate>)delegate { SEL runCommandSelector = @selector(runCommand:withArguments:delegate:); NSInvocation * invocation = [NSInvocation invocationWithMethodSignature: [self methodSignatureForSelector:runCommandSelector]]; [invocation setTarget:self]; [invocation setSelector:runCommandSelector]; if ( !command ) return nil; [invocation setArgument:&command atIndex:2]; if ( arguments ) [invocation setArgument:&arguments atIndex:3]; if ( delegate ) [invocation setArgument:&delegate atIndex:4]; return invocation; } -(void)P4_observeCallbackThread { if ( !_callbackThread ) return; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(P4_threadWillExit:) name:NSThreadWillExitNotification object:_callbackThread]; } -(void)P4_unobserveCallbackThread { if ( !_callbackThread ) return; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSThreadWillExitNotification object:_callbackThread]; } -(void)P4_threadWillExit:(NSNotification*)notification { [self P4_unobserveCallbackThread]; self.callbackThread = nil; } #pragma mark properties /*===================================================================\ | | | Properties | | | \===================================================================*/ -(NSString*)p4port { return [NSString stringWithUTF8String:self.api->GetPort().Text()]; } -(void)setP4port:(NSString *)s { if ( s == nil ) return; self.api->SetPort([s UTF8String]); } -(void)setRequestedProtocol:(NSDictionary *)protocol { if ( _requestedProtocol == protocol ) return; [_requestedProtocol release]; _requestedProtocol = [protocol copy]; for (NSString * key in _requestedProtocol) { NSObject * obj = [_requestedProtocol objectForKey:key]; const char * utf8Key = [key UTF8String]; if ( obj == [NSNull null] ) { self.api->SetProtocol(utf8Key, ""); } else if ( [obj isKindOfClass:[NSString class]] ) { self.api->SetProtocol(utf8Key, [(NSString*)obj UTF8String]); } else if ( [obj isKindOfClass:[NSNumber class]] ) { self.api->SetProtocol(utf8Key, [[(NSNumber*)obj stringValue] UTF8String]); } } } -(NSObject*)serverProtocolValueForKey:(NSString*)key { StrPtr * val = self.api->GetProtocol([key UTF8String]); if ( !val ) return nil; return [NSString stringWithStrPtr:*val]; } -(NSString*)user { return [NSString stringWithUTF8String:self.api->GetUser().Text()]; } -(void)setUser:(NSString *)s { if ( s == nil ) s = @""; self.api->SetUser([s UTF8String]); } -(NSString*)client { return [NSString stringWithUTF8String:self.api->GetClient().Text()]; } -(void)setClient:(NSString *)s { if ( s == nil ) s = @""; self.api->SetClient([s UTF8String]); } -(NSString*)charset { return [NSString stringWithUTF8String:self.api->GetCharset().Text()]; } -(void)setCharset:(NSString *)s { if ( s == nil ) { self.api->SetCharset(""); self.api->SetTrans( CharSetApi::NOCONV ); return; } const char * cString = [s UTF8String]; CharSetApi::CharSet cs = CharSetApi::Lookup( cString ); if ( cs == -1 ) return; self.api->SetCharset(cString); self.api->SetTrans( CharSetApi::UTF_8, cs, CharSetApi::UTF_8, CharSetApi::UTF_8 ); } -(NSString*)currentWorkingDirectory { return [NSString stringWithUTF8String:self.api->GetCwd().Text()]; } -(void)setCurrentWorkingDirectory:(NSString *)s { if ( s == nil ) s = @""; self.api->SetCwd([s UTF8String]); } -(NSString*)hostname { return [NSString stringWithUTF8String:self.api->GetHost().Text()]; } -(void)setHostname:(NSString *)s { if ( s == nil ) s = @""; self.api->SetHost([s UTF8String]); } -(NSString*)password { return [NSString stringWithUTF8String:self.api->GetPassword().Text()]; } -(void)setPassword:(NSString *)s { if ( s == nil ) s = @""; self.api->SetPassword([s UTF8String]); } -(void)setTicketFilePath:(NSString *)ticketFilePath { if ( ticketFilePath == _ticketFilePath ) return; // There is no getter in the C++ Client Api so we // keep a copy around for the getter [_ticketFilePath autorelease]; _ticketFilePath = [ticketFilePath copy]; self.api->SetTicketFile([_ticketFilePath UTF8String]); } -(void)setCallbackThread:(NSThread *)thread { if ( thread == _callbackThread ) return; [self P4_unobserveCallbackThread]; [_callbackThread autorelease]; _callbackThread = [thread retain]; [self P4_observeCallbackThread]; } #pragma mark readonly properties -(NSString *)configurationFilename { return [NSString stringWithUTF8String:self.api->GetConfig().Text()]; } -(BOOL)connected { return ( _connected && !self.api->Dropped() ); } @end #pragma mark utility NSString * StringConstant( const char * cString ) { // NSStrings are toll-free bridges with CFStringRef return (NSString *)CFStringCreateWithCStringNoCopy( kCFAllocatorDefault, cString, kCFStringEncodingUTF8, kCFAllocatorDefault ); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 20722 | jdputsch | initial branch, prep for -Zapp= support | ||
//guest/michael_bishop/MacMenu/src/P4ObjectLayer/p4objc-2009.1.205670/api/P4ClientApi.mm | |||||
#1 | 8331 | Matt Attaway |
Adding initial version of MacMenu for Perforce MacMenu is a helpful Perforce client that sits in your toolbar. It allows you to run standard Perforce operations on the document that is open the currently active editor/viewer. |