/* * P4.Net * Copyright (c) 2006 Shawn Hladky Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; using System.Text; using p4dn; using System.Diagnostics; using P4API.Exceptions; namespace P4API { /// /// Delegate to handle the OnPrompt event. /// /// Sender /// P4PromptEventArgs /// Handle Perforce "prompts". Prompts are from commands that ask the user to respond, such as: /// ///
  • login
  • ///
  • passwd
  • ///
  • resolve (without an -a* switch)
  • ///
    ///
    /// public delegate void OnPromptEventHandler(object sender, P4PromptEventArgs args); /// /// A connection to a Perforce server instance. /// /// public class P4Connection : IDisposable { private ClientApi m_ClientApi; private bool _tagged = true; private bool _Initialized; private string _CallingProgram; private string _CallingProgramVersion; private string _Client = null; private string _Port = null; private string _User = null; private string _Host = null; private string _CWD = null; private string _Charset = null; private DateTime _p4epoch = new DateTime(1970, 1, 1); private TimeSpan _offset; private bool _timespanSet = false; private P4ExceptionLevels _exceptionLevel = P4ExceptionLevels.NoExceptionOnWarnings ; /// /// Raised when Perforce is prompting for a response. /// /// Handle Perforce "prompts". Prompts are from commands that ask the user to respond, such as: /// ///
  • login
  • ///
  • passwd
  • ///
  • resolve (without an -a* switch)
  • ///
    ///
    /// public event OnPromptEventHandler OnPrompt; private ClientApi _ClientAPI { get { if (m_ClientApi == null) { m_ClientApi = new ClientApi(); } //if (!_Initialized) throw new Exceptions.ServerNotConnected(); return m_ClientApi; } //set { m_ClientApi = value; } } /// /// Initializes a new instance of the class. /// public P4Connection() { this._Initialized = false; this._CallingProgram = "P4.Net API"; this._CallingProgramVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); } private TimeSpan _TimeOffset { get { if (!_timespanSet) { try { P4Record info = Run("info")[0]; string offset = info.Fields["serverDate"]; offset = offset.Split(' ')[2]; int sepPosition = offset.Length - 2; int offsetMinutes = int.Parse(offset.Substring(sepPosition)); int offsetHours = int.Parse(offset.Substring(0,sepPosition)); _offset = new TimeSpan(offsetHours, offsetMinutes, 0); _timespanSet = true; } catch { throw; } } return _offset; } } /// /// Gets/Sets the client workspace. /// /// Many Perforce commands require a valid client spec to be set in order to run. public string Client { get { return _ClientAPI.Client; } set { _Client = value; _ClientAPI.SetClient(value); } } /// /// Gets/Sets the current working directory. /// /// Setting CWD can be used so that relative paths are specified. Keep in mind that changing /// the CWD also might cause P4CONFIG settings to change. public string CWD { get { return _ClientAPI.Cwd; } set { _CWD = value; _ClientAPI.SetCwd(value); } } /// /// Gets/Sets the Exception level when running Perforce commands. /// /// This property controls when P4.Net will throw exceptions /// when the underlying Perforce commands raise errors and warnings. /// The default is public P4ExceptionLevels ExceptionLevel { get { return _exceptionLevel; } set { _exceptionLevel = value; } } /// /// Converts Perforce date (integer) to .Net DateTime. /// /// The Perforce API returns most dates as an integer representing the number of seconds /// since 1/1/1970. It is in UTC. The command line client generally returns dates as strings /// in the Perforce server's time zone. The ConvertDate methods use the server's time zone for /// the offset, not necessarily the local time zone. /// DateTime in .Net format. public DateTime ConvertDate(int p4Date) { DateTime utc = _p4epoch.AddSeconds(p4Date); return utc.Add(_TimeOffset); } /// /// Converts Perforce date (integer value as a string) to .Net DateTime. /// /// The Perforce API returns most dates as an integer representing the number of seconds /// since 1/1/1970. It is in UTC. The command line client generally returns dates as strings /// in the Perforce server's time zone. The ConvertDate methods use the server's time zone for /// the offset, not necessarily the local time zone. public DateTime ConvertDate(string p4Date) { return ConvertDate(int.Parse(p4Date)); } /// /// Converts .Net DateTime to Perforce date (integer). /// /// The Perforce API returns most dates as an integer representing the number of seconds /// since 1/1/1970. It is in UTC. The command line client generally returns dates as strings /// in the Perforce server's time zone. The ConvertDate methods use the server's time zone for /// the offset, not necessarily the local time zone. public int ConvertDate(DateTime date) { TimeSpan ts = date.Subtract(_p4epoch); ts = ts.Subtract(_TimeOffset); return ts.Days*24*60*60 + ts.Hours*60*60 + ts.Minutes*60 + ts.Seconds; } /// /// Gets/Sets the Host-name of the client. /// /// Forcing the Host to a different name is useful when using a client that /// was defined on a different host. Of course, that can cause issues with the client's /// have list, so use with caution. public string Host { get { return _ClientAPI.Host; } set { _Host = value; _ClientAPI.SetHost(value); } } /// /// Gets/Sets the Perforce Server port. /// /// The port. public string Port { get { return _ClientAPI.Port; } set { if (_Initialized) throw new ServerAlreadyConnected(); _Port = value; _ClientAPI.SetPort(value); } } /// /// Gets/Sets the User login used to connect to Perforce. /// /// The user. public string User { get { return _ClientAPI.User; } set { _User = value; _ClientAPI.SetUser(value); } } /// /// Gets/Sets the client character set. /// /// Internationalized servers are not yet fully supported! /// The client's charset. public string Charset { get { return _ClientAPI.Charset; } set { _Charset = value; _ClientAPI.SetCharset(value); } } /// /// Get/Sets the name of the calling program. /// /// This value is seen in p4 monitor and in the server log files. /// Defaults to "P4.Net API" public string CallingProgram { get { return _CallingProgram; } set { if (_Initialized) throw new ServerNotConnected_SetVar_AfterInit(); _CallingProgram = value; // Set these in the initialize section } } /// /// Gets/Sets the version of the calling program's version. /// /// This value is seen in p4 monitor and the server log files. /// Defaults to the assembly version of the P4API.dll. public string CallingVersion { get { return _CallingProgramVersion; } set { if (_Initialized) throw new ServerNotConnected_SetVar_AfterInit(); _CallingProgramVersion = value; // Set these in the initialize section } } /// /// Sets the password to conenct with. /// /// Do not set this value with a server running security level 2 or higher. Use the Login() method. public string Password { set { _ClientAPI.SetPassword(value); } } /// /// Sets the ticket file used for Authentication. /// public string TicketFile { set { // _ClientAPI.SetTicketFile(value); } } /// /// Connect to the Perforce server /// public void Connect() { EstablishConnection(_tagged); } /// /// Disconnect from the Perforce Server /// public void Disconnect() { CloseConnection(); } /// /// Login to the Perforce Server /// /// The password. public void Login(string password) { P4ExceptionLevels oldLevel = _exceptionLevel; try { P4UnParsedRecordSet r = new P4UnParsedRecordSet(); r.LoginPassword = password; EstablishConnection(false); string[] Args = { }; _exceptionLevel = P4ExceptionLevels.NoExceptionOnErrors; RunIt("login", Args, r.ResultClientUser); //for good measure delete the password r.LoginPassword = null; if (r.HasErrors()) { throw new P4API.Exceptions.InvalidLogin(r.ErrorMessage); } } finally { _exceptionLevel = oldLevel; } } /// /// Creates a new pending changelist. /// /// The description. /// P4PendingChangelist object representing the named pending changelist public P4PendingChangelist CreatePendingChangelist(string Description) { EstablishConnection(false); return new P4PendingChangelist(Description, this); } /// /// Fetch a form object from Perforce. /// /// The form command. /// The args. /// /// A Form object. The fields of the form can be read or updated. If you update a filed, you can save it with Save_Form. /// /// public P4Form Fetch_Form(string FormCommand, params string[] Args) { P4FormRecordSet r = new P4FormRecordSet(FormCommand); EstablishConnection(true); string[] AllArgs = new string[Args.Length + 1]; AllArgs[0] = "-o"; for (int i = 0; i < Args.Length ; i++) { if (Args[i] == "-o") { throw new InvalidFormArgument(); } AllArgs[i+1] = Args[i]; } RunIt(FormCommand, AllArgs, r.ResultClientUser); // The Fetch_Form command must always throw an error // because there will be no form to return if there's a // problem if (r.HasErrors()) { throw new FormFetchException(FormCommand, r.ErrorMessage); } return r.Form; } /// /// Saves the form to Perforce. /// /// The P4Form object retrieved from Fetch_Form. /// /// P4UnParsedRecordSet. Output can be parsed to verify the form was processed correctly. public P4UnParsedRecordSet Save_Form(P4Form Form) { return Save_Form(Form, false); } /// /// Saves the form to Perforce. /// /// The P4Form object retrieved from Fetch_Form. /// True to pass the '-f' flag when saving. /// /// P4UnParsedRecordSet. Output can be parsed to verify the form was processed correctly. public P4UnParsedRecordSet Save_Form(P4Form Form, bool Force) { P4UnParsedRecordSet r = new P4UnParsedRecordSet(); r.SpecDef = Form.SpecDef; r.FormInput = Form.AllFieldDictionary ; EstablishConnection(true); if (Force) { string[] Args = { "-i", "-f" }; RunIt(Form.FormCommand, Args, r.ResultClientUser, true); } else { string[] Args = { "-i"}; RunIt(Form.FormCommand, Args, r.ResultClientUser, true); } if ((( _exceptionLevel == P4ExceptionLevels.ExceptionOnBothErrorsAndWarnings || _exceptionLevel == P4ExceptionLevels.NoExceptionOnWarnings) && r.HasErrors()) || ( _exceptionLevel == P4ExceptionLevels.ExceptionOnBothErrorsAndWarnings && r.HasWarnings()) ) { throw new RunUnParsedException(r); } return r; } /// /// Determines if the Perforce connection is valid. /// /// if set to true it will check if the user is logged in. /// if set to true it will verify the client exists. /// /// true if the connection is valid; otherwise, false. /// /// /// IsValidConnection performs the following 3 checks (depending on the arguments passed). /// ///
  • Runs p4 info, and verifies the server is valid.
  • ///
  • If checkClient is true, verifies that p4 info returns a clientName that is not "*unknown*.
  • ///
  • If checkLogin is true, verifies that p4 login -s does not have errors.
  • ///
    ///
    public bool IsValidConnection(bool checkLogin, bool checkClient) { P4ExceptionLevels oldExLevel = this.ExceptionLevel; try { this.ExceptionLevel = P4ExceptionLevels.NoExceptionOnErrors; P4RecordSet r = Run("info"); if (r.HasErrors()) return false; if (r.Records.Length != 1) return false; if (checkClient) { if (!r.Records[0].Fields.ContainsKey("clientName")) return false; if (r.Records[0].Fields["clientName"] == "*unknown*") return false; } if (checkLogin) { P4UnParsedRecordSet ur = RunUnParsed("login", "-s"); if (ur.HasErrors()) return false; } } catch { // something went way wrong return false; } finally { // set the ExceptionLevel back this.ExceptionLevel = oldExLevel; } return true; } /// /// Executes a Perforce command in tagged mode. /// /// The command. /// The arguments to the Perforce command. Remember to use a dash (-) in front of all switches /// A P4Recordset containing the results of the command. public P4RecordSet Run(string Command, params string[] Args) { P4RecordSet r = new P4RecordSet(); EstablishConnection(true); RunIt(Command, Args, r.ResultClientUser); if ((( _exceptionLevel == P4ExceptionLevels.ExceptionOnBothErrorsAndWarnings || _exceptionLevel == P4ExceptionLevels.NoExceptionOnWarnings) && r.HasErrors()) || ( _exceptionLevel == P4ExceptionLevels.ExceptionOnBothErrorsAndWarnings && r.HasWarnings() ) ) { throw new RunException(r); } return r; } /// /// Executes a Perforce command in non-tagged mode. /// /// The command. /// The args. /// public P4UnParsedRecordSet RunUnParsed(string Command, params string[] Args) { P4UnParsedRecordSet r = new P4UnParsedRecordSet(); P4BaseRecordSet.OnPromptEventHandler handler = new P4BaseRecordSet.OnPromptEventHandler(this.HandleOnPrompt); r.OnPrompt += handler; EstablishConnection(false); RunIt(Command, Args, r.ResultClientUser); r.OnPrompt -= handler; if ((( _exceptionLevel == P4ExceptionLevels.ExceptionOnBothErrorsAndWarnings || _exceptionLevel == P4ExceptionLevels.NoExceptionOnWarnings) && r.HasErrors()) || ( _exceptionLevel == P4ExceptionLevels.ExceptionOnBothErrorsAndWarnings && r.HasWarnings() ) ) { throw new RunUnParsedException(r); } return r; } private void HandleOnPrompt(object sender, P4PromptEventArgs e) { e.Response = RaiseOnPromptEvent(e.Message); } #region Private Helper Methods private void EstablishConnection(bool tagged) { // If we're switching contexts from tagged to un-tagged, // then we need to reset the connection if ((m_ClientApi != null) && tagged != _tagged) { CloseConnection(); } if (m_ClientApi == null) { m_ClientApi = new ClientApi(); } if (m_ClientApi.Dropped()!=0) { //Console.WriteLine("Dropped!"); CloseConnection(); } if (!_Initialized) { Error err = null; try { _tagged = tagged; err = new Error(); if (tagged) m_ClientApi.SetProtocol("tag", ""); if (tagged) m_ClientApi.SetProtocol("specstring", ""); //May have lost our settings... reset here if (_Client != null) m_ClientApi.SetClient(_Client); if (_User != null) m_ClientApi.SetUser(_User); if (_CWD != null) m_ClientApi.SetCwd(_CWD); if (_Charset != null) m_ClientApi.SetCharset(_Charset); if (_Host != null) m_ClientApi.SetHost(_Host); if (_Port != null) m_ClientApi.SetPort(_Port); m_ClientApi.Init(err); if (err.IsFatal()) { //Trace.WriteLine(err.Fmt()); //Trace.WriteLine("Unable to connect to Perforce!"); throw new Exception("Unable to connect to Perforce!"); } if (_CallingProgram != null) m_ClientApi.SetProg(_CallingProgram); if (_CallingProgramVersion != null) m_ClientApi.SetVersion(_CallingProgramVersion); _Initialized = true; err.Dispose(); } catch (Exception e) { //Trace.WriteLine(e.Message); m_ClientApi.Final(err); err.Dispose(); m_ClientApi.Dispose(); m_ClientApi = null; throw new PerforceInitializationError(e.Message); } } } private void CloseConnection() { // Need to reset the connection if (_Initialized) { Error err = new Error(); m_ClientApi.Final(err); err.Dispose(); } if (m_ClientApi != null) { m_ClientApi.Dispose(); m_ClientApi = null; } _Initialized = false; } private void RunIt(string command, string[] args, ClientUser cu) { RunIt(command, args, cu, false ); } private void RunIt(string command, string[] args, ClientUser cu, bool SaveForm) { m_ClientApi.SetArgv(args); if (SaveForm) { m_ClientApi.SaveForm(command, cu); } else { m_ClientApi.Run(command, cu); } } // Wrap event invocations inside a protected virtual method // to allow derived classes to override the event invocation behavior private string RaiseOnPromptEvent(string Message) { // Make a temporary copy of the event to avoid possibility of // a race condition if the last subscriber unsubscribes // immediately after the null check and before the event is raised. OnPromptEventHandler handler = OnPrompt; // Event will be null if there are no subscribers if (handler != null) { P4PromptEventArgs e = new P4PromptEventArgs(Message); // Use the () operator to raise the event. handler(this, e); return e.Response; } else { return string.Empty; } } #endregion #region IDisposable Members /// /// Calls Disconnect. /// public void Dispose() { this.Disconnect(); } #endregion } }