Click or drag to resize

Introduction

Pre-requisites

For a project to use P4API.NET, p4api.net.dll and p4bridge.dll must be in the build output directory. On creation of a new console application, the Debug output location would be: ...\SolutionDir\ProjectDir\bin\Debug\. Next, add p4api.net.dll as a project reference by clicking Add Reference... on the References node of the project in the solution explorer in Visual Studio and browsing to the location of the newly added p4api.net.dll.



Using P4API.NET

Client programs interact with the Perforce server by:

  1. Initializing a connection.

  2. Sending commands.

  3. Closing the connection.



Initializing a connection

To connect to the Perforce server, your client application must call the Connect() method; for example:

// initialize the connection variables
// note: this is a connection without using a password
string uri = "localhost:6666";
string user = "admin";
string ws_client = "admin_space";


// define the server, repository and connection
Server server = new Server(new ServerAddress(uri));
Repository rep = new Repository(server);
Connection con = rep.Connection;


// use the connection variables for this connection
con.UserName = user;
con.Client = new Client();
con.Client.Name = ws_client;


// connect to the server
con.Connect(null);
// initialize the connection variables
// note: this is a connection without using a password
// that sets the application name and version.
// This information will appear when commands are
// recorded in the server log as
// [ProgramName/ProgramVersion]
string uri = "localhost:6666";
string user = "admin";
string ws_client = "admin_space";


// define the server, repository and connection
Server server = new Server(new ServerAddress(uri));
Repository rep = new Repository(server);
Connection con = rep.Connection;


// use the connection variables for this connection
con.UserName = user;
con.Client = new Client();
con.Client.Name = ws_client;


// set the program name and version
P4.Options options = new P4.Options();
options["ProgramName"] ="MyP4API.NET_APP";
options["ProgramVersion"] ="2015.1.105.4164";


// connect to the server
con.Connect(options);
// initialize the connection variables
// note: this is a connection using a password
string uri = "localhost:6666";
string user = "admin";
string ws_client = "admin_space";
string pass = "password";


// define the server, repository and connection
Server server = new Server(new ServerAddress(uri));
Repository rep = new Repository(server);
Connection con = rep.Connection;


// use the connection variables for this connection
con.UserName = user;
con.Client = new Client();
con.Client.Name = ws_client;


// connect to the server
con.Connect(null);


// login to the server to get credential
// (using null for options and user params)
Credential cred = con.Login(pass, null, null);


// get server metadata and check version
// (using null for options parameter)
ServerMetaData p4info = rep.GetServerMetaData(null);
ServerVersion version= p4info.Version;
string release = version.Major;

For information on SSL connections, refer to the TrustAndConnect method.



Setting Command Time Out

The Command Time Out duration defaults to 5 seconds. If a command times out, P4API.NET will throw a P4CommandTimeOutException. After a connection has been established the Command Time Out can be changed in Repository.Connection.CommandTimeout:

// initialize the connection variables
// note: this is a connection without using a password
string uri = "localhost:6666";
string user = "admin";
string ws_client = "admin_space";


// define the server, repository and connection
Server server = new Server(new ServerAddress(uri));
Repository rep = new Repository(server);
Connection con = rep.Connection;


// use the connection variables for this connection
con.UserName = user;
con.Client = new Client();
con.Client.Name = ws_client;


// connect to the server
con.Connect(null);


// change the Command Time Out to 20 seconds
con.CommandTimeout = TimeSpan.FromSeconds(20);



Sending commands

To send commands to the Perforce server, your client can use P4 API.NET methods; for example:

// set the options for the p4 changes command
string clientName = "admin_space";
int maxItems = 5;
string userName = "admin";
ChangesCmdOptions options = new ChangesCmdOptions(ChangesCmdFlags.LongDescription,
        clientName, maxItems, ChangeListStatus.None, userName);


// run the command against the current repository
IList<Changelist> changes = rep.getChangelists(options);


// changes will contain the data returned by the command
// p4 changes -L -c admin_space -m 5 -u admin
// set the options for the p4 edit command
string depotPath = "//depot/main/test.txt";
int changelist = 0;
// create FileSpec with null for ClientPath, LocalPath, and VersionSpec
FileSpec fileToCheckout = new FileSpec(new DepotPath(depotPath), null, null, null)
// using null for FileType
EditCmdOptions options = new EditCmdOptions(EditFilesCmdFlags.None, changelist, null);


// run the command with the current connection
IList<FileSpec> filesCheckedOut = con.Client.EditFiles(options, fileToCheckout);


// filesCheckedOut will contain the data returned by the command
// p4 edit //depot/main/test.txt
// and the file will be checked out in the default pending changelist
// set the options for the p4 submit command
// 0 to specify default pending changelist, null for changelist since we are using the default
string description = "update to test.txt";
// set reopen to false
ClientSubmitOptions clientOptions = new ClientSubmitOptions(false,SubmitType.SubmitUnchanged)
SubmitCmdOptions options = new SubmitCmdOptions(SubmitFilesCmdFlags.None,
                    0, null, description, clientOptions)
// create FileSpec with null for ClientPath, LocalPath, and VersionSpec
string depotPath = "//depot/main/test.txt";
FileSpec fileToSubmit = new FileSpec(new DepotPath(depotPath), null, null, null)


// run the command with the current connection
SubmitResults results= con.Client.SubmitFiles(options, fileToSubmit);


// results will contain the data returned by the command
// p4 submit -d "update to test.txt" //depot/main/test.txt



Closing the connection

To disconnect from the Perforce server, your client application must call the Disconnect() method; for example:

// disconnect from the server
con.Disconnect();



Running commands directly

To run commands that do not have methods in the .NET API, your client can use the P4Command.Run() method; for example:

// create a new command using parameters:
// repository, command, tagged, arguments
string file="//depot/main/test.txt";
P4Command cmd = new P4Command(rep, "attribute", true, file);
Options opts = new Options();
opts["-n"] = "fileID";
opts["-v"] = "1";


//run command and get results
P4CommandResult results = cmd.Run(opts);


// results will contain the data returned by the command
// p4 -ztag attribute -n fileID -v 1 //depot/main/test.txt

Running command methods is more useful when care about the results of the command and want them parsed for display or to be passed on to other commands. If command results are not important, running the command directly may be preferable.



Working with Options

Options have subclasses specific to particular commands. These dictionary objects can also be created directly, for example:

ClientCmdOptions clientOpts = new ClientCmdOptions(ClientCmdFlags.Output);


will create the same options as:


Options opts = new Options();
opts["-o"]=null;


A command run with either of these options will use the -o flag. Refer to the Options Constructors for details on specific options flags.


Note that some option flags are mutually exclusive and using them together will result in the same errors that would be returned from the command line. Some examples of proper usage:


// the SubmitCmdOptions class
public SubmitCmdOptions(SubmitFilesCmdFlags flags, int changelist, Changelist newChangelist,
string description, ClientSubmitOptions submitOptions)

// options for submitting from the default changelist
// the SubmitCmdOptions and ClientSubmitOptions can vary
SubmitCmdOptions submitOpts = new SubmitCmdOptions(SubmitFilesCmdFlags.None, 0, null, "submitting
from default changelist", new ClientSubmitOptions(false,SubmitType.SubmitUnchanged))

// options for submitting from a numbered changelist 16
// the SubmitCmdOptions and ClientSubmitOptions can vary
SubmitCmdOptions submitOpts = new SubmitCmdOptions(SubmitFilesCmdFlags.IncludeJobs, 16, null,
null, new ClientSubmitOptions(false,SubmitType.RevertUnchanged));

// options for submitting a new changelist using a changelist specification where change
// is a P4.Changelist
// the SubmitCmdOptions and ClientSubmitOptions can vary
SubmitCmdOptions submitOpts = new SubmitCmdOptions(SubmitFilesCmdFlags.ReopenFiles, 0, change,
null, new ClientSubmitOptions(true,SubmitType.SubmitUnchanged));

// options for submitting a shelved file from a numbered changelist 18
// the SubmitCmdOptions and ClientSubmitOptions can vary
SubmitCmdOptions submitOpts = new SubmitCmdOptions(Perforce.P4.SubmitFilesCmdFlags.SubmitShelved,
18, null, null, null);


Here is an example of improper usage of flags that are mutually exclusive:


// the SubmitCmdOptions class
public SubmitCmdOptions(SubmitFilesCmdFlags flags, int changelist, Changelist newChangelist,
string description, ClientSubmitOptions submitOptions)

// improper options for submitting from a numbered changelist 20
// the SubmitCmdOptions and ClientSubmitOptions can vary
SubmitCmdOptions submitOpts = new SubmitCmdOptions(SubmitFilesCmdFlags.None, 16, null,
"submitting change 20", new ClientSubmitOptions(false,SubmitType.RevertUnchanged));


The above options will return the error "Usage: submit [ -r -f option ] -c changelist#\n" because a changelist number and a description were specified and the -c and -d flags are mutually exclusive.



Building a FileSpec

Commands for working with files and paths will need a FileSpec or a list or an array of FileSpecs as an argument when running the command. A FileSpec consists of a PathSpec (or PathSpecs) and a VersionSpec. The PathSpecs can be DepotPaths, ClientPaths, and LocalPaths. The VersionSpec can be a revision or a revision range. Some examples:

// create a FileSpec for //depot/test.txt
// using new FileSpec(PathSpec path, VersionSpec version)
PathSpec path = new DepotPath("//depot/test.txt");
FileSpec depotFile = new FileSpec(path, null);

// create a FileSpec for //depot/test.txt#5
// using new FileSpec(PathSpec path, VersionSpec version)
path = new DepotPath("//depot/test.txt");
VersionSpec version = new Revision(5);
depotFile = new FileSpec(path, version);

// create a FileSpec for //depot/test.txt@16,@22
// (version range between changelists 16 and 22)
// using new FileSpec(PathSpec path, VersionSpec version)
path = new DepotPath("//depot/test.txt");
VersionSpec lowerChangeID = new ChangelistIdVersion(16);
VersionSpec upperChangeID = new ChangelistIdVersion(22);
version = new VersionRange(lowerChangeID,upperChangeID);
depotFile = new FileSpec(path, version);

// create a FileSpec for C:\Users\username\Depot\test.txt#head
// using new FileSpec(PathSpec path, VersionSpec version)
path = new LocalPath("C:\\Users\\username\\Depot\\test.txt");
version = new HeadRevision();
depotFile = new FileSpec(path, version);

// create a FileSpec for //depot/test.txt@labelName
// using new FileSpec(PathSpec path, VersionSpec version)
path = new DepotPath("//depot/test.txt");
version = new LabelNameVersion("labelName");
depotFile = new FileSpec(path, version);

// create a FileSpec for //depot/...@2013/5/27,@now
// (version range between 2013/5/27 and now)
// using new FileSpec(PathSpec path, VersionSpec version)
path = new DepotPath("//depot/...");
DateTimeVersion lowerTimeStamp = new DateTimeVersion(new DateTime(2013,5,27));
DateTimeVersion upperTimeStamp = new DateTimeVersion(DateTime.Now);
version = new VersionRange(lowerTimeStamp,upperTimeStamp);
depotFile = new FileSpec(path, version);


Refer to the VersionSpec class for additional revision types.



Error handling

If a command returns a null, there were likely errors. Errors have different severity levels and do not necessarily mean that a command has failed. P4 API.NET methods will throw a P4Exception if an error of severity E_FAILED or higher is returned. This setting can be changed in P4Exception.MinThrowLevel. For example:

// turn off P4Exceptions
P4Exception.MinThrowLevel = ErrorSeverity.E_NOEXC;


The following is an example of a command that will throw a P4Exception:


try
{
    // set options for client command
    ClientCmdOptions clientOpts = new ClientCmdOptions(ClientCmdFlags.Switch);

    // attempt to get label with p4 -s label testLabel
    Label label = rep.GetLabel("testLabel", null, clientOpts);
}
catch (P4Exception ex)
{
    // catch exception and display error message
    Console.WriteLine(ex.Message);
}


Because the error that the command returns is of severity E_FAILED an exception will be thrown. The Message displayed is: Usage: label [ -d -f -g -i -o -t template ] labelname Invalid option: -s. since there is no -s flag for p4 label.


This is an example of a command that will not throw a P4Exception:


// set options for client command
 try
{
    SyncFilesCmdOptions syncOpts = new SyncFilesCmdOptions(SyncFilesCmdFlags.None, 1);
    FileSpec depotFile = new FileSpec(new DepotPath("//depot/test.txt"), null, null, null);
    IList<FileSpec> syncedFiles = rep.Connection.Client.SyncFiles(syncOpts, depotFile);
}
catch (P4Exception ex)
{
    Console.WriteLine(ex.Message);
}


Because the error that the command returns is of severity E_WARN no exception will be thrown. The file, //depot/test.txt is already at the latest revision but the command has still succeeded with a warning of "//depot/test.txt - file(s) up-to-date." syncedFiles will be null.



Sample application: a console specification fetcher

This is a console application that connects to a Perforce server and returns Perforce specification information based on user input of 2 words: specification type, specification name. It uses Connection.Login(password) for working with a server at security level 3. Lower security levels should also work and connecting with a user without a password can be done by passing a blank string for password. There is minimal error handling for failed Perforce commands. Since -o is used, specifications that do not yet exist may be returned on entry of a non-existent specification name.



Create a new C# Console Application project and paste the following code into Program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Perforce.P4;
namespace apiConsoleApplication
{
    class Program
    {
        static void Main()
        {
            string uri = "";
            string user = "";
            string workspace = "";
            string pass = "";

            bool exit = false;

            Server server = new Server(new ServerAddress(uri));
            Repository rep = new Repository(server);

            // establish a connection to a server
            while(rep.Connection.Status==ConnectionStatus.Disconnected)
            {
                // get user input for connection
                Console.WriteLine("port:");
                uri = Console.ReadLine();
                Console.WriteLine("");
                Console.WriteLine("user:");
                user = Console.ReadLine();
                Console.WriteLine("");
                Console.WriteLine("client:");
                workspace = Console.ReadLine();
                Console.WriteLine("");
                Console.WriteLine("password:");
                pass = Console.ReadLine();
                Console.WriteLine("");

                server = new Server(new ServerAddress(uri));
                rep = new Repository(server);
                Connection con = rep.Connection;
                con.UserName = user;
                con.Client = new Client();
                con.Client.Name = workspace;

                // connect
                bool connected = con.Connect(null);
                if (connected)
                {
                    try
                    {
                        // attempt a login
                        Credential cred = con.Login(pass);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        con.Disconnect();
                        continue;
                    }
                    // get p4 info and show successful connection
                    ServerMetaData info = rep.GetServerMetaData(null);
                    Console.WriteLine("CONNECTED TO " + info.Address.Uri);
                    Console.WriteLine("");
                }
                else
                {
                    // retry the prompt for connection info
                    continue;
                }
            }

            while(rep.Connection.Status==ConnectionStatus.Connected&&!(exit))
            {
                Options opts= new Options();
                opts["-o"] = null;
                string input = Console.ReadLine();
                exit = (input == "exit");
                string[] command = input.Split(' ');
                switch (command[0])
                {
                        // on user input of job <job name> get job
                        // and output to console
                    case "job":
                        try
                        {
                            Job job = rep.GetJob(command[1], opts);
                            Console.WriteLine("");
                            Console.WriteLine(job.ToString());
                            Console.WriteLine("");
                            break;
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("");
                            Console.WriteLine(ex.Message);
                            Console.WriteLine("");
                            break;
                        }

                        // on user input of change <change number> get
                        // change and output to console
                    case "change":
                        try
                        {
                            opts = new Options();
                            int id = 0;
                            int.TryParse(command[1], out id);

                            Changelist change = rep.GetChangelist(id, opts);
                            Console.WriteLine("");
                            Console.WriteLine(change.ToString());
                            Console.WriteLine("");
                            break;
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("");
                            Console.WriteLine(ex.Message);
                            Console.WriteLine("");
                            break;
                        }

                        // on user input of client <client name> get
                        // client and output to console
                    case "client":
                        try
                        {
                            Client client = rep.GetClient(command[1]);
                            Console.WriteLine("");
                            Console.WriteLine(client.ToString());
                            Console.WriteLine("");
                            break;
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("");
                            Console.WriteLine(ex.Message);
                            Console.WriteLine("");
                            break;
                        }

                        // on user input of label <label name> get
                        // label and output to console
                    case "label":
                        try
                        {
                            Label label = rep.GetLabel(command[1]);
                            Console.WriteLine("");
                            Console.WriteLine(label.ToString());
                            Console.WriteLine("");
                            break;
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("");
                            Console.WriteLine(ex.Message);
                            Console.WriteLine("");
                            break;
                        }

                        // on user input of stream <stream path>get
                        // stream and output to console
                    case "stream":
                        try
                        {
                            Stream stream = rep.GetStream(command[1]);
                            Console.WriteLine("");
                            Console.WriteLine(stream.ToString());
                            Console.WriteLine("");
                            break;
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("");
                            Console.WriteLine(ex.Message);
                            Console.WriteLine("");
                            break;
                        }

                        // on user input of branch <branch name> get
                        // branch and output to console
                    case "branch":
                        try
                        {
                            BranchSpec branch = rep.GetBranchSpec(command[1]);
                            Console.WriteLine("");
                            Console.WriteLine(branch.ToString());
                            Console.WriteLine("");
                            break;
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("");
                            Console.WriteLine(ex.Message);
                            Console.WriteLine("");
                            break;
                        }
                }
            }
        }
    }
}