/******************************************************************************* 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 | | 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*)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((const void *)bytes) length:length encoding:NSUTF8StringEncoding]; // NSString * s = [[[NSString alloc] initWithBytes:const_cast((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]; // 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]; // return [[[NSError alloc] initWithDomain:P4ErrorDomain // code:code // userInfo:userInfo] autorelease]; }