/*******************************************************************************
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 : P4ClientApiPriv.mm
|
| Author : Michael Bishop <mbishop@perforce.com>
|
| Description: Objective-C wrapper for the Perforce API. The private
* methods to handle raw server output.
\===================================================================*/
#import "P4ClientApiPriv.h"
#import "P4ClientApi.h"
#ifdef DEBUG
// #define LOG_ALL_OUTPUT
#endif
SEL HandleErrorSelector = @selector(clientApi:didReceiveError:);
SEL OutputInfoSelector = @selector(clientApi:didReceiveSimpleServerMessage:level:);
SEL OutputFStatSelector = @selector(clientApi:didReceiveTaggedResponse:);
SEL OutputBinarySelector = @selector(clientApi:didReceiveBinaryContent:);
SEL OutputTextSelector = @selector(clientApi:didReceiveTextContent:);
SEL FinishedSelector = @selector(clientApiDidFinishCommand:);
/*===================================================================\
| |
| IMPLEMENTATION |
| |
\===================================================================*/
ClientUserWrapper::ClientUserWrapper( P4ClientApi * api, id delegate )
: ClientUser()
, mApi(api)
, mDelegate( delegate )
, mDelegateIsAnNSObject( false )
{
// delegates are not retained and the api is not retained to prevent
// a loop of retain counts, which cannot be deleted.
mDelegateIsAnNSObject = [delegate isKindOfClass:[NSObject class]];
inspectDelegateMethods();
}
ClientUserWrapper::~ClientUserWrapper()
{
mApi = nil;
mDelegate = nil;
}
void
ClientUserWrapper::inspectDelegateMethods()
{
if ( mDelegate == nil )
return;
mHasHandleError = [mDelegate respondsToSelector:HandleErrorSelector];
mHasOutputInfo = [mDelegate respondsToSelector:OutputInfoSelector];
mHasOutputBinary = [mDelegate respondsToSelector:OutputBinarySelector];
mHasOutputText = [mDelegate respondsToSelector:OutputTextSelector];
mHasOutputStat = [mDelegate respondsToSelector:OutputFStatSelector];
mHasFinished = [mDelegate respondsToSelector:FinishedSelector];
}
NSThread *
ClientUserWrapper::callbackThread() const
{
if ( mApi.callbackThread )
return mApi.callbackThread;
return [NSThread currentThread];
}
/*===================================================================\
| |
| Callback Methods |
| |
\===================================================================*/
#pragma mark ClientUser callbacks
void
ClientUserWrapper::HandleError( Error * error )
{
if ( !mHasHandleError )
{
ClientUser::HandleError(error);
return;
}
if ( !error->Test() )
return;
NSError * err = NSErrorFromError(error);
NSThread * thread = callbackThread();
// We can only call back to other threads if the delegate is an NSObject
// NSProxy objects cannot handle this.
if ( !thread || !mDelegateIsAnNSObject )
{
[mDelegate clientApi:mApi didReceiveError:err];
return;
}
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:
[(NSObject*)mDelegate methodSignatureForSelector:HandleErrorSelector]];
[invocation setTarget:mDelegate];
[invocation setSelector:HandleErrorSelector];
[invocation setArgument:&mApi atIndex:2];
[invocation setArgument:&err atIndex:3];
[invocation retainArguments];
[invocation performSelector:@selector(invoke)
onThread:thread
withObject:nil waitUntilDone:YES];
#ifdef LOG_ALL_OUTPUT
NSLog( @"%@: %@", mApi.p4port, err );
#endif
}
void
ClientUserWrapper::OutputInfo( char level, const char *data )
{
if ( !mHasOutputInfo )
{
ClientUser::OutputInfo(level, data);
return;
}
NSString * output = StringFromUtf8CString( data );
NSThread * thread = callbackThread();
// We can only call back to other threads if the delegate is an NSObject
// NSProxy objects cannot handle this.
if ( !thread || !mDelegateIsAnNSObject )
{
[mDelegate clientApi:mApi didReceiveSimpleServerMessage:output level:level];
return;
}
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:
[(NSObject*)mDelegate methodSignatureForSelector:OutputInfoSelector]];
[invocation setTarget:mDelegate];
[invocation setSelector:OutputInfoSelector];
[invocation setArgument:&mApi atIndex:2];
[invocation setArgument:&output atIndex:3];
[invocation setArgument:&level atIndex:4];
[invocation retainArguments];
[invocation performSelector:@selector(invoke)
onThread:thread
withObject:nil waitUntilDone:YES];
#ifdef LOG_ALL_OUTPUT
NSLog( @"%@: %@", mApi.p4port, output );
#endif
}
void
ClientUserWrapper::OutputBinary( const char *data, int length )
{
if ( !mHasOutputBinary )
{
ClientUser::OutputBinary(data, length);
return;
}
NSData * d = [[NSData alloc] initWithBytesNoCopy:const_cast<void *>((void*)data)
length:length
freeWhenDone:NO];
NSThread * thread = callbackThread();
// We can only call back to other threads if the delegate is an NSObject
// NSProxy objects cannot handle this.
if ( !thread || !mDelegateIsAnNSObject )
{
[mDelegate clientApi:mApi didReceiveBinaryContent:d];
return;
}
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:
[(NSObject*)mDelegate methodSignatureForSelector:OutputBinarySelector]];
[invocation setTarget:mDelegate];
[invocation setSelector:OutputBinarySelector];
[invocation setArgument:&mApi atIndex:2];
[invocation setArgument:&d atIndex:3];
[invocation retainArguments];
[invocation performSelector:@selector(invoke)
onThread:thread
withObject:nil waitUntilDone:YES];
[d release];
}
void
ClientUserWrapper::OutputText( const char *data, int length )
{
if ( !mHasOutputText )
{
ClientUser::OutputText(data, length);
return;
}
NSString * s = StringFromUtf8Bytes( data, length );
NSThread * thread = callbackThread();
// We can only call back to other threads if the delegate is an NSObject
// NSProxy objects cannot handle this.
if ( !thread || !mDelegateIsAnNSObject )
{
[mDelegate clientApi:mApi didReceiveTextContent:s];
return;
}
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:
[(NSObject*)mDelegate methodSignatureForSelector:OutputTextSelector]];
[invocation setTarget:mDelegate];
[invocation setSelector:OutputTextSelector];
[invocation setArgument:&mApi atIndex:2];
[invocation setArgument:&s atIndex:3];
[invocation retainArguments];
[invocation performSelector:@selector(invoke)
onThread:thread
withObject:nil waitUntilDone:YES];
#ifdef LOG_ALL_OUTPUT
NSLog( @"%@: %@", mApi.p4port, s );
#endif
}
void
ClientUserWrapper::OutputStat( StrDict *varList )
{
if ( !mHasOutputStat )
{
ClientUser::OutputStat(varList);
return;
}
NSDictionary * dict = DictionaryFromStrDict(varList);
NSThread * thread = callbackThread();
// We can only call back to other threads if the delegate is an NSObject
// NSProxy objects cannot handle this.
if ( !thread || !mDelegateIsAnNSObject )
{
[mDelegate clientApi:mApi didReceiveTaggedResponse:dict];
return;
}
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:
[(NSObject*)mDelegate methodSignatureForSelector:OutputFStatSelector]];
[invocation setTarget:mDelegate];
[invocation setSelector:OutputFStatSelector];
[invocation setArgument:&mApi atIndex:2];
[invocation setArgument:&dict atIndex:3];
[invocation retainArguments];
[invocation performSelector:@selector(invoke)
onThread:thread
withObject:nil waitUntilDone:YES];
#ifdef LOG_ALL_OUTPUT
NSLog( @"%@: %@", mApi.p4port, dict );
#endif
}
void
ClientUserWrapper::Finished()
{
if ( !mHasFinished )
{
ClientUser::Finished();
return;
}
NSThread * thread = callbackThread();
// We can only call back to other threads if the delegate is an NSObject
// NSProxy objects cannot handle this.
if ( !thread || !mDelegateIsAnNSObject )
{
[mDelegate clientApiDidFinishCommand:mApi];
return;
}
[(NSObject*)mDelegate performSelector:FinishedSelector
onThread:thread
withObject:mApi waitUntilDone:YES];
}
/*===================================================================\
| |
| Utility Conversion Methods |
| |
\===================================================================*/
#pragma mark conversions
NSString *
StringFromUtf8Bytes( const char * bytes, int length )
{
if ( !bytes )
return nil;
NSString * s = [[[NSString alloc] initWithBytes:const_cast<void *>((const void *)bytes)
length:length
encoding:NSUTF8StringEncoding] autorelease];
// I'd really like to be able to do this without creating copies but
// it'd be up to the receiving end to make a copy if it needed to. It
// would also have to be documented well
return s;
}
NSString *
StringFromUtf8StrPtr( const StrPtr * str )
{
if ( !str )
return nil;
return StringFromUtf8Bytes( str->Text(), str->Length() );
}
NSString *
StringFromUtf8CString( const char * str )
{
if ( !str )
return nil;
return StringFromUtf8Bytes( str, strlen(str) );
}
NSDictionary *
DictionaryFromStrDict( StrDict * strDict )
{
if ( !strDict )
return nil;
// Convert the StrDict to an NSDictionary
//
NSMutableDictionary * dict = [[[NSMutableDictionary alloc] init] autorelease];
StrRef var, val;
int i = 0;
while ( strDict->GetVar( i, var, val ) )
{
NSString * v = StringFromUtf8StrPtr( &val );
NSString * k = StringFromUtf8StrPtr( &var );
[dict setValue:v forKey:k];
i++;
}
return [NSDictionary dictionaryWithDictionary:dict]; // passes an immutable dictionary
}
NSError *
NSErrorFromError( Error * error )
{
if ( !error )
return nil;
StrBuf message;
error->Fmt(message, EF_PLAIN);
NSString * localizedMessage = StringFromUtf8StrPtr( &message );
ErrorId * errorId = error->GetId(0);
int code = errorId->code;
NSValue * subSystem = [NSNumber numberWithInt:errorId->Subsystem()];
NSValue * subCode = [NSNumber numberWithInt:errorId->SubCode()];
NSValue * severity = [NSNumber numberWithInt:errorId->Severity()];
NSValue * generic = [NSNumber numberWithInt:errorId->Generic()];
NSDictionary * userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
localizedMessage, NSLocalizedDescriptionKey,
subSystem, P4SubsystemErrorKey,
subCode, P4SubCodeErrorKey,
severity, P4SeverityErrorKey,
generic, P4GenericErrorKey,
nil];
return [[[NSError alloc] initWithDomain:P4ErrorDomain
code:code
userInfo:userInfo] autorelease];
}