/*******************************************************************************
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 "i18napi.h"
#import "p4tags.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 = NSErrorFromError( &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 = NSErrorFromError( &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 || [args count] == 0)
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
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]);
if ( self.prefersTaggedOutput )
self.api->SetVar( P4Tag::v_tag, "" );
if ( self.rowScanningLimit > 0 )
self.api->SetVar( P4Tag::v_maxScanRows, self.rowScanningLimit );
if ( self.tableLockingTimeLimit > 0 )
self.api->SetVar( P4Tag::v_maxLockTime, self.tableLockingTimeLimit );
if ( self.returnedResultsLimit > 0 )
self.api->SetVar( P4Tag::v_maxResults, self.returnedResultsLimit );
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;
}
-(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 StringFromUtf8StrPtr(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);
// We assume everything coming back from the server is in utf8 except
// file content which we allow to be translated
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 );
}