/******************************************************************************* 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" #import "P4TypeConversions.hpp" #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 InputTextSelector = @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 = [NSError errorWithP4Error:*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( @"Error: %@: %@", mApi.p4port, err ); #endif } void ClientUserWrapper::OutputInfo( char level, const char *data ) { if ( !mHasOutputInfo ) { ClientUser::OutputInfo(level, data); return; } NSString * output = [NSString stringWithUTF8String: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( @"Info: %@: %@", 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 = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease]; 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( @"Text: %@: %@", mApi.p4port, s ); #endif } void ClientUserWrapper::OutputStat( StrDict *varList ) { if ( !mHasOutputStat ) { ClientUser::OutputStat(varList); return; } NSDictionary * dict = [NSDictionary dictionaryWithStrDict:*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( @"Stat: %@: %@", mApi.p4port, dict ); #endif } void ClientUserWrapper::InputData( StrBuf *buf, Error *e ) { // This is not thread safe NSString * inputString = nil; [mDelegate clientApi:mApi didRequestInputAsText:&inputString]; if ( inputString ) { buf->Set( [inputString UTF8String] ); return; } buf->Clear(); e->Set(E_FAILED, "No text to input"); } 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]; }