/*******************************************************************************
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.H
|
| Author : Michael Bishop <mbishop@perforce.com>
|
| Description: Objective-C wrapper for the Perforce API.
\===================================================================*/
#import <CoreFoundation/CoreFoundation.h>
/*!
\interface P4ClientApi
\brief P4ClientApi is an Objective-C wrapper around the
<a href="http://www.perforce.com/perforce/doc.current/manuals/p4api/index.html">C++ ClientApi</a>.
This documentation is a supplement to the more complete Perforce C++ API documentation
available at: <a href="http://www.perforce.com/perforce/doc.current/manuals/p4api">http://www.perforce.com/perforce/doc.current/manuals/p4api</a>
\see <a href="http://kbmain.perforce.com/AllPerforceApplications/PerforceCApi">Perforce C++ Client API Knowledge Base Articles</a>.
\section OVERVIEW Overview
To retrieve data from a Perforce server you must complete three tasks:
-# Establish a connection
-# Run one (or more) commands
-# Disconnect
In addition to those tasks, there are a list of properties you may
set on the api instance to affect its operation.
\section SCHEDULING_ISSUES Scheduling Issues
The Perforce C++ API is a blocking API; that is, when a method is
executed to connect to, or retrieve data from the server, that method
does not return until the data has been sent and the command is finished.
This is usually unacceptable in a graphical user environment.
The Mac's solution to this is to create a network request and add it as
a source to the current RunLoop. The core Perforce C++ API has not been
written to support this mechanism directly. Instead you must execute the
client api requests in a separate thread so the current thread does not
block and send the results back to the originating thread.
Typically, this involves a lot of work to package mesages so they can be
sent across threads. Much care has been taken to handle as much of this
for you as possible. You are still responsible for scheduling the client
api request in a separate thread (to retain the most scheduling control)
but you can specify the thread to which the server results will be sent
and the return messages will be packaged up and sent to your delegate
in that thread.
Convenience methods to create NSInvocation objects for key client api
methods are provided for your use. You can add these invocations to an
NSOperationQueue and let the system schedule them, or you can ask the
invocation object itself to execute in another thread using NSObject's
<tt>performSelectorInBackground:withObject:</tt> (and variants).
Below is an example of blocking, and non-blocking ways of connecting to
the server:
BLOCKING:
\code
P4ClientApi * api = [[[P4ClientApi alloc] init] autorelease];
NSError * e;
// Your thread could block for a while if the server is not running
if (![api connectToPort:@"perforce:1666" withProtocol:nil error:&e] )
return e;
... run the command
\endcode
NON-BLOCKING:
\code
P4ClientApi * api = [[P4ClientApi alloc] init];
// Instead, of calling the method directly, create an invocation to be
// executed in another thread
api.callbackThread = [NSThread currentThread];
NSInvocation * invocation = [api invocationForConnectToPort:@"perforce:1666"
protocol:nil
delegate:self];
NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithInvocation:invocation];
// the operation queue will schedule the operation in another thread and return immediately
[operationQueue addOperation:operation];
[operation release];
...
-(void)clientApi:(P4ClientApi *) api
didFailToConnectWithError:(NSError*)s
{
NSLog(@"failed to connect to %@", api.p4port);
[api release];
}
-(void)clientApiDidConnect:(P4ClientApi*)api
{
... run the command on the client api
}
\endcode
\section HANDLING_ERRORS Error Handling
The Perforce Objective-C API wraps up native Perforce errors into an NSError
object. It does so in the following manner:
-# The domain of the NSError is set to: \p P4ErrorDomain.
-# The formatted message from the C++ Error object is passed in the NSError
object as the \p localizedDescription.
-# The code of the first ErrorId in the C++ Error is placed into the NSError \p code.
You can use this to compare with the included ErrorId instances in the C++ header files.
-# Additionally, you can see the pieces of the above ErrorId by looking in the userInfo field of
the NSError and finding the NSValues for these keys:
P4SubsystemErrorKey, P4SubCodeErrorKey, P4SeverityErrorKey, P4GenericErrorKey
*/
/// \mainpage P4ClientApi
/// \copydoc P4ClientApi
#import <Foundation/Foundation.h>
/// \brief establishes the NSError domain for Perforce client errors
extern NSString * const P4ErrorDomain;
/// \brief Key to retrieve the subsystem code of an Error from Perforce
extern NSString * const P4SubsystemErrorKey;
/// \brief Key to retrieve the code (within a subsystem) of an Error from Perforce
extern NSString * const P4SubCodeErrorKey;
/// \brief Key to retrieve the severity of an error from Perforce
extern NSString * const P4SeverityErrorKey;
/// \brief Key to retrieve the generic code of an Error from Perforce
extern NSString * const P4GenericErrorKey;
/// \brief Used to notify the server of the protocol level the client
/// understands
/// \see <a href="http://kb.perforce.com/P4dServerReference/ProtocolLevels/PerforceClientLevels">Perforce Protocol Levels</a>
extern NSString * const P4ProtocolVersion;
@protocol P4ClientApiConnectionDelegate;
@protocol P4ClientApiCommandDelegate;
@interface P4ClientApi : NSObject {
@private
void * _clientApi; // untyped to prevent C++ header inclusion
void * _currentClientUser; // untyped to prevent C++ header inclusion
NSString * _currentCommand;
NSArray * _currentArgs;
id _userInfo;
NSThread * _callbackThread;
BOOL _connected;
NSString * _ticketFilePath;
NSString * _programIdentifier;
NSString * _version;
NSDictionary * _requestedProtocol;
BOOL _prefersTaggedOutput;
int _rowScanningLimit;
int _tableLockingTimeLimit;
int _returnedResultsLimit;
}
/*! \name Connecting
Use these to establish a connection to the server.
*/
//@{
/*!
\brief Establish a connection and prepare to run commands.
Use this method to establish a connection to a Perforce server. It must be
made before running any commands with runCommand:withArguments:delegate:
The protocol is an NSDictionary of keys and values. The values can be of these types
NSString, NSNumber, NSNull. They will all be converted to strings when
sent to the server.
\note This method does not return until after a connection is made or unsuccessful.
For unsucessful connections, this could last 5 seconds, blocking your
event-loop. You may wish to instead call
connectToPort:withProtocol:delegate: in another thread and
have it return messages to your current thread.
\param port a P4PORT value or \p nil to use the default.
\param requestedProtocol A dictionary of protocol values to configure the connection.
You may pass \p nil if you don't have a specific protocol
requirement.
\param error a pointer to an NSError pointer or \p nil
(see \ref HANDLING_ERRORS)
\result YES if the connection was completed
\result NO if the connection could not be made. An error will be returned in
\p which the caller must retain.
\note Internally, this is equivalent to calling
<tt>ClientApi::SetPort();
ClientApi::SetProtocol();
ClientApi::Init();</tt>
\see connectToPort:withProtocol:delegate:
\see ClientApi::SetPort()
\see ClientApi::SetProtocol()
\see ClientApi::Init()
*/
-(BOOL)connectToPort:(NSString*)port
withProtocol:(NSDictionary*)requestedProtocol
error:(NSError **)error;
/*!
\brief Establish a connection and prepare to run commands.
Use this method to establish a connection to a Perforce server. It must be
made before running any commands with runCommand:withArguments:delegate:
The protocol is an NSDictionary of keys and values. The values can be of these types
NSString, NSNumber, NSNull. They will all be converted to strings when
sent to the server.
\note This method does not return until after a connection is made or unsuccessful.
For unsucessful connections, this could last 5 seconds, blocking your
event-loop. You may wish to instead call it in another thread and
have it return messages in your current thread to your delegate.
\param port a P4PORT value or \p nil to use the default.
\param requestedProtocol A dictionary of protocol values to configure the connection.
You may pass \p nil if you don't have a specific protocol
requirement.
\param delegate a delegate object that is used to process messages from the server.
It is not retained.
\note Internally, this is equivalent to calling
<tt>ClientApi::SetPort();
ClientApi::SetProtocol();
ClientApi::Init();</tt>
\see connectToPort:withProtocol:error:
\see ClientApi::SetPort()
\see ClientApi::SetProtocol()
\see ClientApi::Init()
*/
-(void)connectToPort:(NSString*)port
withProtocol:(NSDictionary*)requestedProtocol
delegate:(id<P4ClientApiConnectionDelegate>)delegate;
/*! \brief Convenience method to create an invocation for use in non-blocking calls.
Use this method to create an NSInvocation that can be called in another
thread or added to an NSOperationQueue as an NSInvocationOperation.
The protocol is an NSDictionary of keys and values. The values can be of these types
NSString, NSNumber, NSNull. They will all be converted to strings when
sent to the server.
\note as is consistent with NSInvocations, the arguments are not
retained by default. Creating an NSInvocationOperation will retain
the arguments automatically.
\param port a P4PORT value or \p nil to use the default.
\param requestedProtocol A dictionary of protocol values to configure the connection.
You may pass \p nil if you don't have a specific protocol
requirement.
\param delegate a delegate object that is used to process messages from the server.
\see connectToPort:withProtocol:delegate:
*/
-(NSInvocation*)invocationForConnectToPort:(NSString*)port
withProtocol:(NSDictionary*)requestedProtocol
delegate:(id<P4ClientApiConnectionDelegate>)delegate;
// @}
/*! \brief The port connected to in connectToPort:withProtocol:error:
\note It could be a Bonjour name.
\see ClientApi::GetPort()
*/
@property(readonly, copy) NSString * p4port;
/*! \brief The current user name.
\see ClientApi::Set/GetUser()
*/
@property(readwrite, copy) NSString * user;
/*! \brief The current client name.
\see ClientApi::Set/GetClient()
*/
@property(readwrite, copy) NSString * client;
/*! \brief The name of the character set used for translation between
the server and the client.
This is required field when talking to a server in unicode mode and required to be
\p nil when communicating with a non-unicode server. Perforce recommends
the value of \p utf8 with Mac OS X-based clients.
For more information on using the P4API with unicode-enabled servers, see
<a href="http://kbmain.perforce.com/P4dServerReference/Internationalization/UnicodeAndP4..sAndAnswers">Unicode and P4API</a>
\note This calls ClientApi::SetTrans() before establishing a connection
*/
@property(readwrite, copy) NSString * charset;
/*! \brief The current working directory.
\see ClientApi::Set/GetCwd()
*/
@property(readwrite, copy) NSString * currentWorkingDirectory;
/*! \brief The password sent to the server for this connection.
\see ClientApi::Set/GetPassword()
*/
@property(readwrite, copy) NSString * password;
/*! \brief Indicates to the server that the client prefers data to be returned via
the clientApi:didReceiveTaggedResponse: method of the command delegate.
This is \p YES by default.
*/
@property(readwrite, assign) BOOL prefersTaggedOutput;
/*! \brief The name of this host. Unless set explicitly may not be available until
after connecting to the server.
\see ClientApi::Set/GetHost()
*/
@property(readwrite, copy) NSString * hostname;
/*! \brief The location of the user's ticketfile
\note This must be the full path to the file and not a directory.
\see ClientApi::Set/GetTicketFile()
*/
@property(readwrite, copy) NSString * ticketFilePath;
/*! \brief The identifier of the application program which will show up in
logging viewed with the <tt>p4 monitor</tt> command and in
server log output.
It's "P4V" below.
\code
45381 P4V/v60 10.0.105.2194 R joeuser joeuser 04:01:53 IDLE none
\endcode
\note Unlike the C++ API, you can set this at anytime.
\see ClientApi::Set/GetProg()
*/
@property(readwrite, copy) NSString * programIdentifier;
/*! \brief The version of the application program which will show up in
logging viewed with the <tt>p4 monitor</tt> command and in
server log output.
When not set, it defaults to the protocol level of the Client API libraries.
It's "v60" below.
\code
45381 P4V/v60 10.0.105.2194 R joeuser joeuser 04:01:53 IDLE none
\endcode
\note Unlike the C++ API, you can set this at anytime.
\see ClientApi::Set/GetProg()
*/
@property(readwrite, copy) NSString * version;
/*! \brief The userInfo object set on the api object as context when connecting
or running commands.
\see runCommand:withArguments:delegate:
*/
@property(readwrite, retain) id userInfo;
/*! \brief The thread in which the client api will send back its responses.
This is useful when executing the client api in another thread
and sending back responses to the main thread for asynchronous
operation.
When the value is \p nil, the api object will call back to whatever
the currently executing thread is.
It's \p nil by default
*/
@property(readwrite, retain) NSThread * callbackThread;
/*! \brief The filename used when Perforce searches for configuration files
from which to retrieve default values.
\see ClientApi::GetConfig()
*/
@property(readonly) NSString * configurationFilename;
/*! \brief The state of the connection. If the connection was dropped or
otherwise disconnected, this will return NO.
\see ClientApi::Dropped()
*/
@property(readonly) BOOL connected;
/*! \name Executing commands
Once you have established a connection to the server,
you can use these to execute commands.
*/
//@{
/*! \brief Executes a command on the Perforce server.
Use this method to actually execute a command on the Perforce server.
Results are returned via the delegate.
\note This method does not return until after after the server has completely processed
the command and has sent clientApiDidFinishCommand: to your delegate. Because
this stops the current RunLoop from continuing, you may find it desirable
to make this call in another thread and have the server send messages back
to the current thread, allowing the RunLoop to continue.
\note Before making thing call, you must have a connection established through
connectToPort:withProtocol:error:
\attention This method is not thread-safe. If you are scheduling this
call by scheduling invocation objects, you must make sure
that this call returns before the next invocation is called.
You can do this by creating a separate NSOperationQueue per
client api instance and making sure the queue's
<tt>maxConcurrentOperationCount</tt> equals 1
before queuing your invocation objects in it.
\param command the command you want to run (eg: "sync").
\param args An array of arguments to be sent with the command. You may pass \p nil if the
\p command doesn't require arguments to execute.
\param delegate a delegate object that is used to process messages from the server.
It is not retained.
\note Internally, this is equivalent to calling
<tt>ClientApi::SetArgs();
ClientApi::RunCmd();</tt>
*/
-(void)runCommand:(NSString *)command
withArguments:(NSArray*)args
delegate:(id<P4ClientApiCommandDelegate>)delegate;
/*! \brief Convenience method to create an invocation for use in non-blocking calls.
Use this method to create an NSInvocation that can be called in another
thread or added to an NSOperationQueue as an NSInvocationOperation.
\note as is consistent with NSInvocations, the arguments are not
retained by default. Creating an NSInvocationOperation will retain
the arguments automatically.
\param command the command you want to run (eg: "sync").
\param args An array of arguments to be sent with the command. You may pass \p nil if the
\p command doesn't require arguments to execute.
\param delegate a delegate object that is used to process messages from the server.
\see runCommand:withArguments:delegate:
*/
-(NSInvocation*)invocationForRunCommand:(NSString *)command
withArguments:(NSArray*)args
delegate:(id<P4ClientApiCommandDelegate>)delegate;
//@}
/*! \name Advanced Performance Options
Use these to control or limit server calculation or output.
*/
//@{
/*! \brief Limits the rows of data considered and prevents the server from
making large-scale scans.
Assigning a value of 0 (the default) has no effect.
\see http://kb.perforce.com/AdminTasks/PerformanceTuning/MaximizingPe..Performance
\see p4 help maxscanrows
*/
@property(readwrite, assign) int rowScanningLimit;
/*! \brief Limits the amount of time spent during data scans to prevent the
server from locking tables for too long.
Assigning a value of 0 (the default) has no effect.
\see http://kb.perforce.com/AdminTasks/PerformanceTuning/MaximizingPe..Performance
\see p4 help maxlocktime
*/
@property(readwrite, assign) int tableLockingTimeLimit;
/*! \brief Limits the rows of resulting data buffered and prevents the server
from using excessive memory.
Assigning a value of 0 (the default) has no effect.
\see http://kb.perforce.com/AdminTasks/PerformanceTuning/MaximizingPe..Performance
\see p4 help maxresults
*/
@property(readwrite, assign) int returnedResultsLimit;
//@}
/*! \name While running commands...
In your P4ClientApiCommandDelegate methods, you can call these
to retrieve the items passed to <tt>runCommand:withArguments:delegate:</tt>...
*/
//@{
/*! \brief The command that the client api instance is executing.
\see runCommand:withArguments:delegate:
*/
@property(readonly, copy) NSString * currentCommand;
/*! \brief The arguments for the currently executing command.
\see runCommand:withArguments:delegate:
*/
@property(readonly, copy) NSArray * currentArguments;
//@}
/*! \name Disconnecting
These terminate your connection to the server, reporting
any residual errors.
*/
//@{
/*! \brief Disconnects from the server and reports disconnection errors.
\param error a pointer to an NSError or \p nil
(see \ref HANDLING_ERRORS)
\result YES if an error was raised
\result NO if the disconnection has no errors.
\see ClientApi::Final()
*/
-(BOOL)disconnect:(NSError **)error;
/*! \brief Disconnects from the server and reports disconnection errors.
\param delegate a delegate object that is used to process messages from the server.
It is not retained.
\see ClientApi::Final()
*/
-(void)disconnectWithDelegate:(id<P4ClientApiConnectionDelegate>)delegate;
/*! \brief Convenience method to create an invocation for use in non-blocking calls.
Use this method to create an NSInvocation that can be called in another
thread or added to an NSOperationQueue as an NSInvocationOperation.
\note as is consistent with NSInvocations, the arguments are not
retained by default. Creating an NSInvocationOperation will retain
the arguments automatically.
\param delegate a delegate object that is used to process messages from the server.
\see disconnectWithDelegate:
*/
-(NSInvocation*)invocationForDisconnectWithDelegate:(id<P4ClientApiConnectionDelegate>)delegate;
//@}
/*! \name Setting and inspecting the server protocol
You can request specific protocol parameters when calling
connectToPort:withProtocol:error:. After running
a command, the server will also send back additional parameters
about the connection.
*/
//@{
/*!
\brief The protocol dictionary passed in to connectToPort:withProtocol:...
\see connectToPort:withProtocol:error:
*/
@property(readonly, copy) NSDictionary * requestedProtocol;
/*!
\brief The protocol parameters (as a property) containing information about
communication between the server and client.
After making a call to runCommand:withArguments:delegate:,
the server sends a dictionary of additional protocol variables back to the
client with additional information about the communication between server
and client.
\note This is \b different from the dictionary passed in to connectToPort:withProtocol:error:
\see ClientApi::GetProtocol()
*/
-(NSObject*)serverProtocolValueForKey:(NSString*)key;
//@}
@end
/*!
\protocol P4ClientApiConnectionDelegate
\brief Implement this in a delegate when establishing a non-blocking
connection to a Perforce server.
connectToPort:withProtocol:error: is a blocking
call that will not return until a connection is made, or fails. The
timeout is roughly 5 seconds. For this reason, you are encouraged to
implement this protocol and make your connection in another thread.
*/
@protocol P4ClientApiConnectionDelegate <NSObject>
@optional
/*!
\brief Called on your delegate when the client has sucessfully
established a connection. You may now start running
commands with
runCommand:withArguments:delegate:
\param api the client api instance calling this method.
*/
-(void)clientApiDidConnect:(P4ClientApi*)api;
/*!
\brief Called on your delegate when the client was not able to establish
a connection. The client may wait for about 5 seconds before
it calls your delegate with this method.
\param api the client api instance calling this method.
\param error the error message which you can report to the user.
(see \ref HANDLING_ERRORS)
*/
-(void)clientApi:(P4ClientApi*)api didFailToConnectWithError:(NSError*)error;
/*!
\brief Called on your delegate when the client disconnects from the
server (by calling disconnectWithDelegate:).
\param api the client api instance calling this method.
\param error the residual errors from the server
(see \ref HANDLING_ERRORS)
*/
-(void)clientApi:(P4ClientApi*)api didDisconnectWithError:(NSError *)error;
@end
/*!
\protocol P4ClientApiCommandDelegate
\brief Implement this to receieve messages from the Perforce server as a
result of calling
runCommand:withArguments:delegate: on a
P4ClientApi instance.
Each method returns a different type of response
depending on the executing command and the configuration of the
P4ClientApi instance. It is closest to the ClientUser class in the C++ API.
\note If a message is not implemented by your delegate class, the
ClientUser behavior is executed by default. That behavior is what
the p4 command-line CLI uses.
Each of of these methods has an analagous callback in the C++ API. They
are documented with each method.
*/
@protocol P4ClientApiCommandDelegate <NSObject>
@optional
/*!
\brief Called on your delegate when the client was not able to establish
a connection. The client may wait for about 5 seconds before
it calls your delegate with this method.
\param api the client api instance calling this method.
\param error the error message which you can report to user.
(see \ref HANDLING_ERRORS)
\see ClientUser::OutputError
*/
-(void)clientApi:(P4ClientApi*)api didReceiveError:(NSError*)error;
/*!
\brief Called on your delegate by the server during most Perforce
commands; its most common use is to display listings of information
about files.
\note The messages from the server in this call are "simple" because
they are meant to be sent directly to the console and do not
include a code for the message.
\param api the client api instance calling this method.
\param message the printable message from the server
\param level the indentation "level" of the output
\see ClientUser::OutputInfo(
*/
-(void)clientApi:(P4ClientApi*)api didReceiveSimpleServerMessage:(NSString*)message level:(char)level;
/*!
\brief Called on your delegate when the server has formatted output.
This is much more useful than a simple message. Normally, only the results
of an "fstat" command come through this method, but you can get many more
commands to output in this way, by setting \p prefersTaggedOutput to
YES on the P4ClientApi instance.
\code
api.prefersTaggedOutput = YES;
\endcode
\param api the client api instance calling this method.
\param response the dictionary containin a structured response.
\see ClientUser::OutputStat
*/
-(void)clientApi:(P4ClientApi*)api didReceiveTaggedResponse:(NSDictionary*)response;
/*!
\brief Called on your delegate typically as a result of a "print" command
on a binary file.
\param api the client api instance calling this method.
\param data the binary data from the server
\see ClientUser::OutputBinary
*/
-(void)clientApi:(P4ClientApi*)api didReceiveBinaryContent:(NSData*)data;
/*!
\brief Called on your delegate typically as a result of a "print" command
on a text file.
\param api the client api instance calling this method.
\param text the text data from the server
\see ClientUser::OutputText(
*/
-(void)clientApi:(P4ClientApi*)api didReceiveTextContent:(NSString*)text;
/*!
\brief Called on your delegate when the server has finished processing a
command.
\param api the client api instance calling this method.
\see ClientUser::Finished()
*/
-(void)clientApiDidFinishCommand:(P4ClientApi*)api;
@end