/******************************************************************************* Copyright (c) 2010, 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 : P4Server.cs * * Author : dbb * * Description : Classes used to wrap calls in the P4Bridge DLL in C#. * ******************************************************************************/ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Text; using System.IO; using System.Timers; using System.Threading; using System.Diagnostics; namespace Perforce.P4 { /// /// Allows a client to monitor the execution of a command. It allow the client to /// cancel the command if it takes to long to complete or display a UI to allow the /// user to cancel the command. /// public interface IKeepAlive { /// /// A command is starting /// /// Server running the command /// CmdId of the command /// Command Line for the command for display purposes /// bool StartQueryCancel(P4Server server, uint cmdId, Thread cmdRunThread,string cmdLine); // Note, it is OK for the API might send this multiple times with the, // same cmdId to ensure that any UI displayed by the client iss dismissed. /// /// The command has completed dismiss any UI or timeouts /// /// CmdId of the command that completed /// /// Note, it is OK for the API might send this multiple times with the, /// same cmdId to ensure that any UI displayed by the client iss dismissed. /// void CommandCompleted(uint cmdId); } /// /// P4Server /// /// Represents the connection to a Perforce Server using the the P4 Bridge /// DLL. It wraps the calls exported by the DLL and transforms the data /// types exported by the DLL as native C#.NET data types. /// public partial class P4Server : IDisposable { public IKeepAlive KeepAlive { get; set; } internal object Sync = new object(); private Dictionary _lastResultsCache; /// /// The results of the last command executed on this thread /// public P4CommandResult LastResults { get { if (_lastResultsCache.ContainsKey(System.Threading.Thread.CurrentThread.ManagedThreadId)) { return _lastResultsCache[System.Threading.Thread.CurrentThread.ManagedThreadId]; } return null; } internal set { if (_lastResultsCache == null) { _lastResultsCache = new Dictionary(); } if (_lastResultsCache.Count > 32) { // if the results cach is getting large, throw awy anything older than 10 seconds DateTime old = DateTime.Now-TimeSpan.FromSeconds(10); foreach (int key in _lastResultsCache.Keys) { if (_lastResultsCache[key].TimeStamp < old) { Debug.Trace(string.Format("Throwing away results for thread, {0}", key)); _lastResultsCache.Remove(key); } } } _lastResultsCache[System.Threading.Thread.CurrentThread.ManagedThreadId] = value; } } /// /// Get the version of the p4.net assembly /// public static System.Version Version { get { return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; } } /// /// Get the error message generated by the previous connection (if any) /// public static P4ClientError ConnectionErrorInt { get { IntPtr pErr = P4Bridge.GetConnectionError(); if (pErr != null) return new P4ClientError( null, pErr ); return null; } } /// /// Get the error message generated by the previous connection (if any) /// from the bridge dll. /// public P4ClientError ConnectionError { get; private set; } private void LogBridgeMessage( int log_level, String file, int line, String message ) { Debug.Trace(String.Format("[{2}]P4Bridge({0}:{1}) {3}", file, line, log_level, message)); // remove the full path to the source, keep just the file name String fileName = Path.GetFileName( file ); string category = String.Format( "P4Bridge({0}:{1})", file, line ); LogFile.LogMessage( log_level, category, message ); } private string _server; private string _user; private string _pass; private string _ws_client; private string _prog_name; private string _prog_ver; private string _cwd; private P4CallBacks.LogMessageDelegate logfn = null; /// /// Create a P4BridgeServer used to connect to the specified P4Server /// /// Host:port for the P4 server. /// User name for the login. /// Can be null/blank if only running commands that do not require /// a login. /// Password for the login. Can be null/blank if /// only running commands that do not require a login. /// Workspace (client) to be used by the /// connection. Can be null/blank if only running commands that do /// not require a login. public P4Server(String server, String user, String pass, String ws_client) : this(server, user, pass, ws_client, null) { } /// /// Create a P4BridgeServer using the PUC specified by the environment /// or a p4config file if one exists. /// /// Current working Directory. Can be null/blank if /// not connecting to the Perforce server using a P4Config file. public P4Server(String cwd) : this(null,null,null,null,cwd) { } /// /// Create a P4BridgeServer used to connect to the specified P4Server /// /// Host:port for the P4 server. /// User name for the login. /// Can be null/blank if only running commands that do not require /// a login. /// Password for the login. Can be null/blank if /// only running commands that do not require a login. /// Workspace (client) to be used by the /// connection. Can be null/blank if only running commands that do /// not require a login. /// Current working Directory. Can be null/blank if /// not connecting to the Perforce server using a P4Config file. internal P4Server(String server, String user, String pass, String ws_client, String cwd) { RunCmdLastContactMap = new Dictionary(); if (string.IsNullOrEmpty(cwd) == false) { string configPath = P4Server.GetConfig(cwd); if ((string.IsNullOrEmpty(configPath)== false) && (configPath != "noconfig") && (System.IO.File.Exists(configPath))) { using (StreamReader sr = new StreamReader(configPath)) { string line = sr.ReadLine(); while (line != null) { line = line.Trim(); if (line.ToUpper().StartsWith("P4PORT=")) { string[] parts = line.Split('='); if (parts.Length >= 2) { server = parts[1]; } } else if (line.ToUpper().StartsWith("P4USER=")) { string[] parts = line.Split('='); if (parts.Length >= 2) { user = parts[1]; } } else if (line.ToUpper().StartsWith("P4CLIENT=")) { string[] parts = line.Split('='); if (parts.Length >= 2) { ws_client = parts[1]; } } line = sr.ReadLine(); } } } } _server = server; _user = user; _pass = pass; _ws_client = ws_client; _cwd = cwd; isUnicode = false; CurrentEncodeing = P4Encoding.ASCII; // connect to the server without passing user/client/password, that way // we can determine if the target server is Unicode enabled or not, so we // can use the correct encoding for those parameters pServer = P4Bridge.ConnectA(server, null, null, null, logfn); if (pServer != IntPtr.Zero) { if (isUnicode = P4Bridge.IsUnicode(pServer)) { IntPtr pLogFn = IntPtr.Zero; logfn = new P4CallBacks.LogMessageDelegate(LogBridgeMessage); if (logfn != null) pLogFn = Marshal.GetFunctionPointerForDelegate(logfn); // if the server supports Unicode, encode the username, password, and workspace // name in UTF-8 using (PinnedByteArray pCwd = MarshalStringToIntPtr(cwd), pUser = MarshalStringToIntPtr(user), pClient = MarshalStringToIntPtr(ws_client)) { P4Bridge.set_cwdW(pServer, pCwd); P4Bridge.set_userW(pServer, pUser); P4Bridge.set_clientW(pServer, pClient); } } else { // if the server does not support Unicode, pass the username, password, and // workspace name in ASCII P4Bridge.set_cwdA(pServer, cwd); P4Bridge.set_userA(pServer, user); P4Bridge.set_clientA(pServer, ws_client); } } if (pServer == IntPtr.Zero) { ConnectionError = ConnectionErrorInt; if (ConnectionError == null) { IntPtr pObj = P4Bridge.GetErrorResults(pServer, 0); P4ClientErrorList _errorList = new P4ClientErrorList(this, pObj); if ((_errorList != null) && (_errorList.Count > 0)) { P4Exception.Throw(_errorList, GetInfoResults(0)); } ConnectionError = new P4ClientError(ErrorSeverity.E_FATAL, string.Format("Unknown error connecting to server, {0}", _server)); } P4Exception.Throw(ConnectionError); return; //throw new ApplicationException(connectionError); } else { ConnectionError = null; } if (isUnicode) { CurrentEncodeing = P4Encoding.utf8; Process proc = new Process(); proc.StartInfo = new ProcessStartInfo(); proc.StartInfo.UseShellExecute = false; proc.StartInfo.FileName = "p4"; proc.StartInfo.Arguments = "set P4CHARSET"; proc.StartInfo.RedirectStandardOutput = true; ; proc.StartInfo.CreateNoWindow = true; proc.Start(); string output = proc.StandardOutput.ReadToEnd(); proc.WaitForExit(); string charset = null; string[] parts = output.Split(new char[] { '=', ' ' }, 3); if ((parts != null) && (parts.Length >= 2) && (parts[1] != "none")) { charset = parts[1]; } SetCharacterSet(Charset[(int)CurrentEncodeing], charset); } apiLevel = P4Bridge.APILevel(pServer); requiresLogin = P4Bridge.UseLogin(pServer); if ((!requiresLogin) && (!string.IsNullOrEmpty(pass))) { using (PinnedByteArray pPass = MarshalStringToIntPtr(pass)) { P4Bridge.set_passwordW(pServer, pPass); } } // Link the callbacks from the bridge dll to their corresponding events SetInfoResultsCallback(); SetTaggedOutputCallback(); SetErrorCallback(); SetTextResultsCallback(); SetBinaryResultsCallback(); SetPromptCallback(); SetResolveCallback(); SetResolveACallback(); // If we were supplied a password, login into the server. If the // server requires a login (security level >= 3), this will prompt // for the password. If login is not required, the command will just // return with a result saying that login is not required. //Login(pass, null); } /// /// Create a P4BridgeServer used to connect to the specified P4Server /// /// Host:port for the P4 server. /// User name for the login. /// Can be null/blank if only running commands that do not require /// a login. /// Password for the login. Can be null/blank if /// only running commands that do not require a login. /// Workspace (client) to be used by the /// connection. Can be null/blank if only running commands that do /// not require a login. public P4Server(String server, String user, String pass, String ws_client, String cwd, String trust_flag, String fingerprint) { RunCmdLastContactMap = new Dictionary(); _server = server; _user = user; _pass = pass; _ws_client = ws_client; _cwd = cwd; CurrentEncodeing = P4Encoding.ASCII; P4CallBacks.LogMessageDelegate logfn = new P4CallBacks.LogMessageDelegate(LogBridgeMessage); // encode the username, password, and workspace name in UTF-8, we // won't know if the client supports Unicode until after connect // returns using (PinnedByteArray pUser = MarshalStringToIntPtr(user), pClient = MarshalStringToIntPtr(ws_client)) { IntPtr pLogFn = IntPtr.Zero; if (logfn != null) pLogFn = Marshal.GetFunctionPointerForDelegate(logfn); pServer = P4Bridge.TrustedConnectW(server, pUser, IntPtr.Zero, pClient, trust_flag, fingerprint, pLogFn); } if (pServer == IntPtr.Zero) { ConnectionError = ConnectionErrorInt; if (ConnectionError == null) { IntPtr pObj = P4Bridge.GetErrorResults(pServer, 0); P4ClientErrorList _errorList = new P4ClientErrorList(this, pObj); if ((_errorList != null) && (_errorList.Count > 0)) { P4Exception.Throw(_errorList, GetInfoResults(0)); } ConnectionError = new P4ClientError(ErrorSeverity.E_FAILED, string.Format("Unknown error connecting to server, {0}", _server)); } P4Exception.Throw(ConnectionError); return; } else { ConnectionError = null; } if (isUnicode = P4Bridge.IsUnicode(pServer)) { CurrentEncodeing = P4Encoding.utf8; SetCharacterSet(Charset[(int)CurrentEncodeing], Charset[(int)P4Encoding.utf16bom]); } apiLevel = P4Bridge.APILevel(pServer); requiresLogin = P4Bridge.UseLogin(pServer); if ((!requiresLogin) && (!string.IsNullOrEmpty(pass))) { using (PinnedByteArray pPass = MarshalStringToIntPtr(pass)) { P4Bridge.set_passwordW(pServer, pPass); } } // Link the callbacks from the bridge dll to their corresponding events SetInfoResultsCallback(); SetTaggedOutputCallback(); SetErrorCallback(); SetTextResultsCallback(); SetBinaryResultsCallback(); SetPromptCallback(); SetResolveCallback(); SetResolveACallback(); // If we were supplied a password, login into the server. If the // server requires a login (security level >= 3), this will prompt // for the password. If login is not required, the command will just // return with a result saying that login is not required. //Login(pass, null); } /// /// Run a login command on the server /// /// User's password /// Success/Failure /// /// If the server requires a login (security level >= 3), this will /// prompt for the password. If login is not required, the command will /// just return with a result saying that login is not required. /// public bool Login(string password, StringList options) { if (!requiresLogin) { // server does not support login command if (!string.IsNullOrEmpty(password)) { using (PinnedByteArray pPass = MarshalStringToIntPtr(password)) { P4Bridge.set_passwordW(pServer, pPass); } return true; } return false; } P4Command login = new P4Command(this, "login", false); login.Responses = new Dictionary(); login.Responses["DefaultResponse"] = password; P4CommandResult results; try { results = login.Run(options); } catch { results = null; return false; } if (!results.Success) { ConnectionError = results.ErrorList[0]; } else { ConnectionError = null; } return results.Success; } /// /// Run a logout command on the server /// /// The -a flag invalidates the ticket on the server. /// Success/Failure /// /// If the server requires a login (security level >= 3), this will /// logout the user and remove the local ticket. /// public bool Logout(StringList options) { P4Command logout = new P4Command(this, "logout", false); P4CommandResult results; try { results = logout.Run(options); } catch { results = null; return false; } if (!results.Success) { ConnectionError = results.ErrorList[0]; } else { ConnectionError = null; } return results.Success; } /// /// Finalizer /// ~P4Server() { Dispose(false); } bool _disposed = false; public void Dispose(bool disposing) { if (_disposed) { return; } if (!disposing) { } Close(); TaggedOutputCallbackFn_Int = null; pTaggedOutputCallbackFn = IntPtr.Zero; P4Bridge.SetTaggedOutputCallbackFn(pServer, IntPtr.Zero); ErrorCallbackFn_Int = null; pErrorCallbackFn = IntPtr.Zero; P4Bridge.SetErrorCallbackFn(pServer, IntPtr.Zero); InfoResultsCallbackFn_Int = null; pInfoResultsCallbackFn = IntPtr.Zero; P4Bridge.SetInfoResultsCallbackFn(pServer, IntPtr.Zero); TextResultsCallbackFn_Int = null; pTextResultsCallbackFn = IntPtr.Zero; P4Bridge.SetTextResultsCallbackFn(pServer, IntPtr.Zero); BinaryResultsCallbackFn_Int = null; pBinaryResultsCallbackFn = IntPtr.Zero; P4Bridge.SetBinaryResultsCallbackFn(pServer, IntPtr.Zero); PromptCallbackFn_Int = null; pPromptCallbackFn = IntPtr.Zero; P4Bridge.SetPromptCallbackFn(pServer, IntPtr.Zero); _disposed = true; } #region IDisposable Members /// /// For IDispose /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /// /// Reconnect to the server in the event the connection is lost /// public void Reconnect() { if (pServer != IntPtr.Zero) { P4Bridge.CloseConnection(pServer); pServer = IntPtr.Zero; } CurrentEncodeing = P4Encoding.ASCII; P4CallBacks.LogMessageDelegate logfn = new P4CallBacks.LogMessageDelegate(LogBridgeMessage); // encode the username, password, and workspace name in UTF-8, we // won't know if the client supports Unicode until after connect // returns using (PinnedByteArray pUser = MarshalStringToIntPtr(_user), pPass = MarshalStringToIntPtr(_pass), pClient = MarshalStringToIntPtr(_ws_client)) { IntPtr pLogFn = IntPtr.Zero; if (logfn != null) pLogFn = Marshal.GetFunctionPointerForDelegate(logfn); pServer = P4Bridge.ConnectW(_server, pUser, pPass, pClient, pLogFn); } if (pServer == IntPtr.Zero) { ConnectionError = ConnectionErrorInt; P4Exception.Throw(ConnectionError); return; } else { ConnectionError = null; } if (isUnicode = P4Bridge.IsUnicode(pServer)) { CurrentEncodeing = P4Encoding.utf8; SetCharacterSet(Charset[(int)CurrentEncodeing], Charset[(int)P4Encoding.utf16bom]); } requiresLogin = P4Bridge.UseLogin(pServer); // Link the callbacks from the bridge dll to their corresponding events SetInfoResultsCallback(); SetTaggedOutputCallback(); SetErrorCallback(); SetTextResultsCallback(); SetBinaryResultsCallback(); SetPromptCallback(); SetResolveCallback(); SetResolveACallback(); if (_cwd != null) { CurrentWorkingDirectory = _cwd; } ProgramName = _prog_name; ProgramVersion = _prog_ver; // *** Don't login using the login command. If we are reconnecting // *** after a timeout, doe not want to risk the login timing out and // *** throwing us in an infinite loop // We've theoretically already logged in, so if using tickets, _pass // should hold the ticket. //Login(_pass, null); } /// /// Close the connection to a P4 Server /// /// /// Called by the Dispose() method /// public void Close() { if (pServer != IntPtr.Zero) { try { //if ((DisconnectQueue != null) && (DisconnectQueue.Count > 0)) //{ // DisconnectQueueItem it = DisconnectQueue.Dequeue(); // while (it != null) // { // P4Bridge.ReleaseConnection(pServer, it.CmdId); // it = DisconnectQueue.Dequeue(); // } //} if (DisconnectTimer != null) { DisconnectTimer.Stop(); DisconnectTimer.Dispose(); } //if (disconnectTriggers != null) //{ // uint[] keys = new uint[disconnectTriggers.Keys.Count]; // disconnectTriggers.Keys.CopyTo(keys, 0); // foreach (uint cmdId in keys) // { // System.Timers.Timer disconnectTrigger = disconnectTriggers[cmdId]; // disconnectTrigger.Stop(); // disconnectTrigger.Dispose(); // disconnectTriggers.Remove(cmdId); // } //} lock (runningCommands) { if (runningCommands.Count > 0) { Debug.Trace( string.Format("In Close(), currently running {0} commands", runningCommands.Count)); // can't canel commands from the origonal list, as that might change the contents // of the list and crash the enumerator List cancelList = new List(); foreach (RunCommandThreadParam rc in runningCommands) { cancelList.Add(rc); } foreach (RunCommandThreadParam rc in cancelList) { P4Bridge.CancelCommand(pServer, rc.cmdId); } Thread.Sleep(TimeSpan.FromSeconds(1)); // wait for commands to exit } } } finally { if (P4Bridge.CloseConnection(pServer) == 0) { Debug.Trace("Could not close connection, commands are still running"); } pServer = IntPtr.Zero; } } } /// /// Need to use Unicode when marshalling to/from the P4 server /// public bool UseUnicode { get { return isUnicode; } } /// /// What API level does the server support /// public int ApiLevel { get { return apiLevel; } } /// /// The server requires a client to use the login command to pass credentials. /// public bool ReqiresLogin { get { return requiresLogin; } } System.Timers.Timer DisconnectTimer = null; private void OnDisconnectTimer(object source, ElapsedEventArgs e) { if (pServer == IntPtr.Zero) { return; } int idleConnections = P4Bridge.FreeConnections(pServer, (UInt64)DateTime.Now.Ticks); if ((runningCommands.Count <= 0) && (idleConnections == 0)) { DisconnectTimer.Stop(); } } //private class DisconnectTrigger : System.Timers.Timer //{ // private DisconnectTrigger() { CmdId = 0; } // public DisconnectTrigger(double idleDisconectTime, uint cmdId) // : base (idleDisconectTime) { CmdId = cmdId; } // public uint CmdId { get; private set; } //} //private Dictionary disconnectTriggers = null; //private void OnDisconnectTrigger(object source, ElapsedEventArgs e) //{ // if (pServer == IntPtr.Zero) // { // return; // } // DisconnectTrigger trigger = source as DisconnectTrigger; // if (trigger != null) // { // FreeConnection(trigger.CmdId); // } // else // { // if (runningCommands.Count > 0) // { // // don't disconnect if commands are still running. // return; // } // P4Bridge.Disconnect(pServer); // } //} public void Disconnect() { if (pServer != IntPtr.Zero) { P4Bridge.Disconnect(pServer); } } private class RunCommandThreadParam { public string cmd; public bool tagged; public String[] args; public int argc; public bool Results; public uint cmdId; public Exception RunCmdException = null; } /// /// Maximum time for a command to run before timing out; /// public TimeSpan KeepAliveDelay = TimeSpan.FromSeconds(5); [Obsolete("Use RunCmdTimeout")] public TimeSpan RunCmdTimout { get { return RunCmdTimeout; } set { RunCmdTimeout = value; } } #if DEBUG // long delays for debugging so it won't time out / disconnect why stepping through code public TimeSpan RunCmdTimeout = TimeSpan.FromSeconds(5000); public double IdleDisconnectWaitTime = 500; // .5 seconds in milliseconds #else public TimeSpan RunCmdTimeout = TimeSpan.FromSeconds(5); public double IdleDisconnectWaitTime = 500; // .5 seconds in milliseconds #endif private Dictionary RunCmdLastContactMap = null; public bool IsCommandPaused(uint cmdId) { return RunCmdLastContactMap[cmdId].IsPaused; } public void PauseRunCmdTimer(uint cmdId) { if (RunCmdLastContactMap == null || !RunCmdLastContactMap.ContainsKey(cmdId)) { return; } RunCmdLastContactMap[cmdId].Pause(); } public void ContinueRunCmdTimer(uint cmdId) { if (RunCmdLastContactMap == null || !RunCmdLastContactMap.ContainsKey(cmdId)) { return; } RunCmdLastContactMap[cmdId].Continue(); } public class CmdContactTimer : IDisposable { private uint cmdId = 0; private int CallbackCnt = 0; private Dictionary RunCmdLastContactMap = null; DateTime LastContact = DateTime.MaxValue; private CmdContactTimer() { } public CmdContactTimer(Dictionary runCmdLastContactMap, uint _cmdId) { if (runCmdLastContactMap == null) { throw new ArgumentNullException("runCmdLastContactMap"); } lock (runCmdLastContactMap) { cmdId = _cmdId; CallbackCnt = 0; LastContact = DateTime.Now; RunCmdLastContactMap = runCmdLastContactMap; RunCmdLastContactMap[cmdId] = this; } } public void Pause() { lock (RunCmdLastContactMap) { if (RunCmdLastContactMap == null || !RunCmdLastContactMap.ContainsKey(cmdId)) { return; } LastContact = DateTime.MaxValue; CallbackCnt++; } } public void Continue() { lock (RunCmdLastContactMap) { if (RunCmdLastContactMap == null || !RunCmdLastContactMap.ContainsKey(cmdId)) { return; } CallbackCnt--; if (CallbackCnt == 0) { LastContact = DateTime.Now; } } } #region IDisposable Members public void Dispose() { lock (RunCmdLastContactMap) { if (RunCmdLastContactMap != null || RunCmdLastContactMap.ContainsKey(cmdId)) { RunCmdLastContactMap.Remove(cmdId); } } } public bool IsPaused { get { lock (RunCmdLastContactMap) { return LastContact == DateTime.MaxValue; } } } public bool OverDue(TimeSpan RunCmdTimeout) { lock (RunCmdLastContactMap) { return ((DateTime.Now - RunCmdLastContactMap[cmdId].LastContact) > RunCmdTimeout); } } #endregion } private void RunCommandThreadProc(object param) { RunCommandThreadParam CmdParams = param as RunCommandThreadParam; if (CmdParams == null) { Debug.Trace(string.Format("RunCommandThreadProc CmdParams is null param != null:{0}", param != null)); return; } try { Debug.Trace(string.Format("Entering RunCommandThreadProc, cmdId = {0}",CmdParams.cmdId)); CmdParams.RunCmdException = null; if (pServer == IntPtr.Zero) { Debug.Trace("RunCommandThreadProc pServer is null"); CmdParams.Results = false; CmdParams.RunCmdException = new P4Exception(ErrorSeverity.E_FATAL, "pServer is null"); return; } if (CmdParams == null) { Debug.Trace("RunCommandThreadProc CmdParams is null"); CmdParams.RunCmdException = new ArgumentNullException("CmdParams"); return; } #if DEBUG_TIMEOUT if (CmdParams.cmd == "TimeOutTest") { double timeout = RunCmdTimeout.TotalSeconds; double delta = 0; if ((CmdParams.argc > 0) && (CmdParams.args.Length >= 1)) { double.TryParse(CmdParams.args[0], out delta); timeout += delta; } Thread.Sleep(TimeSpan.FromSeconds(timeout)); CmdParams.Results = true; return; } else if (CmdParams.cmd == "LongTimeOutTest") { int timeout = (int) RunCmdTimeout.TotalSeconds; int delta = 0; if ((CmdParams.argc > 0) && (CmdParams.args.Length >= 1)) { int.TryParse(CmdParams.args[0], out delta); delta *= 1000; } DateTime endTime = DateTime.Now + TimeSpan.FromSeconds(timeout*2); while (DateTime.Now < endTime) { Thread.Sleep(delta); InfoResultsCallback_Int(0, IntPtr.Zero); } CmdParams.Results = true; return; } #endif Debug.Trace(string.Format("RunCommandThreadProc CmdParams.cmdId = {0}", CmdParams.cmdId)); if (!isUnicode) { CmdParams.Results = P4Bridge.RunCommandA( pServer, CmdParams.cmd, CmdParams.cmdId, CmdParams.tagged, CmdParams.args, CmdParams.argc); } else { using (PinnedByteArrays args_b = MarshalStringArrayToIntPtrArray(CmdParams.args, CmdParams.argc)) { CmdParams.Results = P4Bridge.RunCommandW( pServer, CmdParams.cmd, CmdParams.cmdId, CmdParams.tagged, (IntPtr[])args_b, CmdParams.argc); } } Debug.Trace(string.Format("RunCommandThreadProc Command finished{1}, CmdParams.Results = {0}", CmdParams.Results, CmdParams.cmdId)); } catch (ThreadAbortException ex) { Debug.Trace(string.Format("RunCommandThreadProc ThreadAbortException caught{2}, {0}/r/n{1}", ex.Message, ex.StackTrace, CmdParams.cmdId)); KeepAlive.CommandCompleted(CmdParams.cmdId); } catch (Exception ex) { Debug.Trace(string.Format("RunCommandThreadProc Exception caught{2}, {0}/r/n{1}", ex.Message, ex.StackTrace, CmdParams.cmdId)); CmdParams.RunCmdException = ex; } finally { // don't let this crash us try { if ((KeepAlive != null) && (CmdParams != null)) { KeepAlive.CommandCompleted(CmdParams.cmdId); } Debug.Trace(string.Format("RunCommandThreadProc finally clause {2}, KeepAlive != null:{0} CmdParams != null:{1}", KeepAlive != null, CmdParams != null, CmdParams.cmdId)); } catch { } } return; } //need a unique id to send to the Client for the IKeepAlive interface private int next_cmdId = 1; private object cmdIdSyncObj = new object(); public uint getCmdId() { lock (cmdIdSyncObj) { if (next_cmdId >= ushort.MaxValue) { next_cmdId = 1; } // even though this is unsigned, dont want a negative integer, so mask off the high bit uint v = (((uint)Thread.CurrentThread.ManagedThreadId) << 16) & 0x7FFF0000; v |= ((uint) Interlocked.Increment(ref next_cmdId)) & 0x0000FFFF; return v; } } private List runningCommands = new List(); /// /// Run a P4 command on the P4 Server /// /// /// If the command fails, the error output will contain one or more /// errors generated by the P4 server. /// /// Command code /// Use tagged output for the results /// Arguments for the command /// Argument count /// Success/Failure public bool RunCommand( string cmd, uint cmdId, bool tagged, String[] args, int argc) { if (_disposed) { throw new P4.P4Exception(ErrorSeverity.E_FATAL, "trying to run a command on a disposed server"); } //if (disconnectTrigger != null) //{ // disconnectTrigger.Stop(); //} bool results = false; RunCommandThreadParam CmdParams = new RunCommandThreadParam(); CmdParams.Results = false; CmdParams.cmd = cmd; CmdParams.tagged = tagged; CmdParams.args = args; CmdParams.argc = argc; CmdParams.cmdId = cmdId; lock (runningCommands) { runningCommands.Add(CmdParams); } try { if ((KeepAlive != null) || (RunCmdTimeout > TimeSpan.Zero)) { using (CmdContactTimer timer = new CmdContactTimer(RunCmdLastContactMap, cmdId)) { ParameterizedThreadStart ts = new ParameterizedThreadStart(RunCommandThreadProc); Thread RunCommandThread = new Thread(ts); RunCommandThread.Name = string.Format("P4API.NET Run Command[{0:X8}]: {1}", cmdId, cmd); RunCommandThread.IsBackground = true; RunCommandThread.Start(CmdParams); if (KeepAlive != null) { TimeSpan delay = KeepAliveDelay; Debug.Trace(string.Format("RunCommand, KeepAlive != null")); while ((delay == TimeSpan.Zero) || (RunCommandThread.Join(delay) == false)) { // set delay to a tenth of a second for later tests delay = TimeSpan.FromSeconds(.1); if (IsCommandPaused(cmdId)) { // we're in a call back, so don't trigure the keep alive, just keep // waiting for the command to complete continue; } string cmdLine = cmd; if ((args != null) && (args.Length > 0)) { for (int idx = 0; idx < args.Length; idx++) { cmdLine += " " + args[idx]; } } // StartQuery won't return until the user cancels or // the command completes. bool canceled = KeepAlive.StartQueryCancel(this, cmdId, RunCommandThread, cmdLine); if (canceled) { KeepAlive.CommandCompleted(cmdId); string msg = string.Format("Command canceled by client: {0} ", cmdLine); throw new P4CommandCanceledException(ErrorSeverity.E_FATAL, msg); } break; } } else { while (RunCommandThread.Join(TimeSpan.FromSeconds(1)) == false) { if (RunCmdLastContactMap[cmdId].OverDue(RunCmdTimeout)) { CancelCommand(cmdId); if (RunCommandThread.Join(TimeSpan.FromSeconds(1)) == false) { RunCommandThread.Abort(); } else { P4Bridge.Disconnect(pServer); } string msg = string.Format("Command time out[{1}]: {0} ", cmd, cmdId); foreach (string arg in args) { if (arg != null) { msg += " " + arg; } } throw new P4CommandTimeOutException(ErrorSeverity.E_FATAL, msg); } } } Debug.Trace(string.Format("RunCommand Command[{1}] finished, CmdParams.Results = {0}", CmdParams.Results ? "true" : "false", cmdId)); results = CmdParams.Results; if (CmdParams.RunCmdException != null) { throw CmdParams.RunCmdException; } } } else { // run command untimed with no option to cancel if (!isUnicode) { results = P4Bridge.RunCommandA(pServer, cmd, cmdId, tagged, args, argc); } else { using (PinnedByteArrays args_b = MarshalStringArrayToIntPtrArray(args, argc)) { results = P4Bridge.RunCommandW(pServer, cmd, cmdId, tagged, (IntPtr[])args_b, argc); } } } if (!results) { Debug.Trace(string.Format("RunCommand Command [{0}] failed!", cmdId)); // error IntPtr pObj = P4Bridge.GetErrorResults(pServer, cmdId); if (pObj == IntPtr.Zero) { // no errors from command, so check for a connection error ConnectionError = ConnectionErrorInt; if (ConnectionError == null) { Debug.Trace(string.Format("RunCommand Command [{0}] failed! ConnectionError=null", cmdId)); P4Exception.Throw(cmd, args, ErrorSeverity.E_FATAL, "Unknown Problem, can't continue"); } else { Debug.Trace(string.Format("RunCommand Command [{1}] failed! ConnectionError={0}", ConnectionError, cmdId)); P4Exception.Throw(cmd, args, ConnectionError); } } else { P4ClientErrorList _errorList = new P4ClientErrorList(this, pObj); P4Exception.Throw(cmd, args, _errorList, GetInfoResults(cmdId)); } return false; } else { // may be some warnings in the list, so fetch it if it is not null IntPtr pObj = P4Bridge.GetErrorResults(pServer, cmdId); P4ClientErrorList _errorList = null; if (pObj != IntPtr.Zero) { _errorList = new P4ClientErrorList(this, pObj); } ConnectionError = null; } } finally { lock (runningCommands) { runningCommands.Remove(CmdParams); } if (IdleDisconnectWaitTime > 0) { if (DisconnectTimer == null) { DisconnectTimer = new System.Timers.Timer(100); DisconnectTimer.AutoReset = true; DisconnectTimer.Elapsed += new ElapsedEventHandler(OnDisconnectTimer); DisconnectTimer.Start(); } else if (DisconnectTimer.Enabled == false) { DisconnectTimer.Start(); } //if (disconnectTriggers == null) //{ // disconnectTriggers = new Dictionary(); //} //DisconnectTrigger disconnectTrigger = null; //if (disconnectTriggers.ContainsKey(cmdId)) //{ // disconnectTrigger = disconnectTriggers[cmdId]; // disconnectTrigger.Stop(); // disconnectTrigger.Dispose(); //} //disconnectTrigger = new DisconnectTrigger(IdleDisconnectWaitTime, cmdId); //disconnectTrigger.AutoReset = false; //disconnectTrigger.Elapsed += new ElapsedEventHandler(OnDisconnectTrigger); //disconnectTrigger.Start(); //disconnectTriggers[cmdId] = disconnectTrigger; } } return results; } /// /// Cancel a running command /// /// Unique Id for the run of the command public void CancelCommand(uint CmdId) { P4Bridge.CancelCommand(pServer, CmdId); } /// /// Release the UI created for a command after it has been completed /// /// Unique Id for the run of the command public void ReleaseConnection(uint CmdId) { P4Bridge.ReleaseConnection(pServer, CmdId, (UInt64)DateTime.Now.AddMilliseconds(IdleDisconnectWaitTime).Ticks); } /// /// Release the UI created for a command after it has been completed /// /// Unique Id for the run of the command internal void ReleaseConnection(uint CmdId, DateTime releaseTime) { P4Bridge.ReleaseConnection(pServer, CmdId, (UInt64)releaseTime.Ticks); } /// /// Free the UI created for a command after it is no longer needed /// /// Unique Id for the run of the command public int FreeConnections() { return P4Bridge.FreeConnections(pServer, (UInt64) DateTime.Now.Ticks); } /// /// Delegate used to send tagged output as it is generated. /// /// /// This delegate will send a complete object after all of its fields /// have been received by the callback from the bridge dll. /// /// /// public delegate void TaggedOutputDelegate( uint cmdId, int ObjId, TaggedObject Obj); /// /// Event to broadcast tagged output /// public event TaggedOutputDelegate TaggedOutputReceived; /// /// Get the tagged output generated by a command /// /// A list of TaggedObjects comprising the tagged output. /// public TaggedObjectList GetTaggedOutput(uint cmdId) { IntPtr pData = P4Bridge.GetTaggedOutput(pServer, cmdId); if (pData == IntPtr.Zero) { return null; } TaggedObjectList objects = new TaggedObjectList(); // use a StrDictListIterator to return all of the objects and // their keys. StrDictListIterator data = new StrDictListIterator(this, pData); while (data.NextItem()) { TaggedObject currentObject = new TaggedObject(); objects.Add(currentObject); KeyValuePair kv = null; while( ( kv = data.NextEntry() ) != null ) { currentObject[kv.Key] = kv.Value; } } P4Bridge.Release(pData); return objects; } /// /// Delegate used to send errors as they are generated. /// /// /// This delegate will send a block of data for each call received by /// the callback from the bridge dll. /// /// Command Id of the command causing the error /// Severity of the error /// Error number for the error /// Error message public delegate void ErrorDelegate(uint cmdId, int severity, int errorNumber, String data); /// /// Holds the call back passed to the bridge used to receive the /// raw data /// P4CallBacks.ErrorDelegate ErrorCallbackFn_Int = null; /// /// Broadcast errors received /// public event ErrorDelegate ErrorReceived; /// /// Get a list of errors (if any) generated by a command /// /// A list of P4ClientErrors, null if no errors public P4ClientErrorList GetErrorResults(uint cmdId) { IntPtr pErr = P4Bridge.GetErrorResults( pServer, cmdId ); if( pErr != IntPtr.Zero ) { return new P4ClientErrorList(this, pErr); } return null; } /// /// Delegate used to send Info Results as they are generated. /// /// /// This delegate will send a block of data for each call received by /// the callback from the bridge dll. /// /// Server supplied message level /// Server supplied message data public delegate void InfoResultsDelegate(uint cmdId, int msgId, int level, String data); /// /// Broadcast event for info results /// public event InfoResultsDelegate InfoResultsReceived; /// /// Get the information messages generated by the previous command /// /// /// Each message is formatted as follows /// l:Message text /// where l is a single digit representing the message level /// /// List of messages public P4ClientInfoMessageList GetInfoResults(uint cmdId) { IntPtr pInfoOut = P4Bridge.GetInfoResults( pServer, cmdId ); if (pInfoOut != IntPtr.Zero) { return new P4ClientInfoMessageList(this, pInfoOut); } return null; } /// /// Delegate used to send Text Results as they are generated. /// /// /// This delegate will send a block of data for each call received by /// the callback from the bridge dll. /// /// Text results generated by the command public delegate void TextResultsDelegate(uint cmdId, String data); /// /// Broadcast event for text results /// public event TextResultsDelegate TextResultsReceived; /// /// Get the complete text results for the last command /// /// public String GetTextResults(uint cmdID) { IntPtr pTextOut = P4Bridge.GetTextResults( pServer, cmdID ); return MarshalPtrToString( pTextOut ); } /// /// Delegate used to send binary output as it is generated. /// /// /// This delegate will send a block of data for each call received by /// the callback from the bridge dll. /// /// Binary data generated by a command public delegate void BinaryResultsDelegate(uint cmdId, byte[] data); /// /// Broadcast event for binary data /// public event BinaryResultsDelegate BinaryResultsReceived; /// /// Get the complete binary results for the last command /// /// The binary data public byte[] GetBinaryResults(uint cmdId) { int byteCount = P4Bridge.GetBinaryResultsCount( pServer, cmdId); if( byteCount <= 0 ) return null; IntPtr pData = P4Bridge.GetBinaryResults( pServer, cmdId); if( pData == IntPtr.Zero ) return null; return MarshalPtrToByteArrary( pData, byteCount ); } /// /// Delegate used to commands as they are executed. /// /// Command line executed by the command public delegate void CommandEchoDelegate(String data); /// /// Broadcast event for text results /// public event CommandEchoDelegate CommandEcho; /// /// Broadcast a the command line (cmd and args) on the CommandEcho event /// /// /// Used to echo an executed command line back to the client /// /// The P4 command. /// The flags and parameters for the command. public void EchoCommand(string cmd, StringList args) { if (CommandEcho != null) { string commandLine = cmd; if (args != null) { for (int idx = 0; idx < args.Count; idx++) { if (args[idx] != null) { commandLine += " " + args[idx]; } } } CommandEcho(commandLine); } } /// /// Broadcast a string on the CommandEcho event /// /// /// Used to echo command data back to the client /// /// The string. public void EchoCommand(string str) { if (CommandEcho != null) { CommandEcho(str); } } /// /// The data set for use by a command /// /// /// If a command requires data not passed on the command line, such as /// a client spec, it is passed to the P$ server by setting the data /// set in the P4 api. /// public void SetDataSet(uint cmdId, string value) { if( isUnicode ) { using( PinnedByteArray pData = MarshalStringToIntPtr( value ) ) { P4Bridge.SetDataSetW( pServer, cmdId, pData ); } } else { P4Bridge.SetDataSetA( pServer, cmdId, value ); } } public String GetDataSet(uint cmdId) { IntPtr pData = P4Bridge.GetDataSet(pServer, cmdId); return MarshalPtrToString(pData); } /// /// Delegate used to provide a custom handler for input prompts from the p4api. /// /// /// /// public delegate String PromptHandlerDelegate(uint cmdId, String msg, bool displayText); /// /// Delegate used to process prompts for input from the server. /// public PromptHandlerDelegate PromptHandler; /// /// Delegate used to provide a custom handler for Resolve callbacks passing a ClientMerge object from the p4api. /// /// public delegate P4ClientMerge.MergeStatus ResolveHandlerDelegate(uint cmdId, P4ClientMerge Merger); /// /// Delegate used to provide a custom handler for Resolve callbacks passing a ClientMerge object from the p4api. /// public ResolveHandlerDelegate ResolveHandler; /// /// Delegate used to provide a custom handler for Resolve callbacks passing a ClientResolve object from the p4api. /// /// public delegate P4ClientMerge.MergeStatus ResolveAHandlerDelegate(uint cmdId, P4ClientResolve Resolver); /// /// Delegate used to provide a custom handler for Resolve callbacks passing a ClientResolve object from the p4api. /// public ResolveAHandlerDelegate ResolveAHandler; /// /// The parameters used by the connection /// /// /// The properties, client, port, user, and password, /// represent the criteria used to connect to a P4 server. If one or /// more is changed, the bridge will drop the current connection if any /// and attempt to connect to the (possibly different) P4 server when /// the next command is executed. If it is desirable to validate the /// connection, execute a command. /// public void SetConnectionData(string port, string user, string password, string client) { _ws_client = client; if (isUnicode) { using (PinnedByteArray pPort = MarshalStringToIntPtr(port), pUser = MarshalStringToIntPtr(user), pPassword = MarshalStringToIntPtr(password), pClient = MarshalStringToIntPtr(client)) { P4Bridge.set_connectionW(pServer, pPort, pUser,pPassword, pClient); } } else { P4Bridge.set_connectionA(pServer, port, user, password, client); } } /// /// The client workspace used by the connection /// /// /// The properties, client, port, user, and password, /// represent the criteria used to connect to a P4 server. If one or /// more is changed, the bridge will drop the current connection if any /// and attempt to connect to the (possibly different) P4 server when /// the next command is executed. If it is desirable to validate the /// connection, execute a command. /// public String Client { get { IntPtr pval = P4Bridge.get_client( pServer ); return MarshalPtrToString( pval ); } set { _ws_client = value; if( isUnicode ) { using( PinnedByteArray pData = MarshalStringToIntPtr( value ) ) { P4Bridge.set_clientW( pServer, pData ); } } else { P4Bridge.set_clientA( pServer, value ); } } } /// /// The user name used by the connection /// /// /// The properties, client, port, user, and password, /// represent the criteria used to connect to a P4 server. If one or /// more is changed, the bridge will drop the current connection if any /// and attempt to connect to the (possibly different) P4 server when /// the next command is executed. If it is desirable to validate the /// connection, execute a command. /// public String User { get { IntPtr pval = P4Bridge.get_user( pServer ); return MarshalPtrToString( pval ); } set { _user = value; if( isUnicode ) { using( PinnedByteArray pData = MarshalStringToIntPtr( value ) ) { P4Bridge.set_userW( pServer, pData ); } } else { P4Bridge.set_userA( pServer, value ); } } } /// /// The hostname:port used by the connection /// /// /// The properties, client, port, user, and password, /// represent the criteria used to connect to a P4 server. If one or /// more is changed, the bridge will drop the current connection if any /// and attempt to connect to the (possibly different) P4 server when /// the next command is executed. If it is desirable to validate the /// connection, execute a command. /// public String Port { get { IntPtr pval = P4Bridge.get_port( pServer ); return MarshalPtrToString( pval ); } set { _server = value; if( isUnicode ) { using( PinnedByteArray pData = MarshalStringToIntPtr( value ) ) { P4Bridge.set_portW( pServer, pData ); } } else { P4Bridge.set_portA( pServer, value ); } } } /// /// The user's password used by the connection /// /// /// The properties, client, port, user, and password, /// represent the criteria used to connect to a P4 server. If one or /// more is changed, the bridge will drop the current connection if any /// and attempt to connect to the (possibly different) P4 server when /// the next command is executed. If it is desirable to validate the /// connection, execute a command. /// public String Password { get { IntPtr pval = P4Bridge.get_password(pServer); return MarshalPtrToString(pval); } set { _pass = value; if (isUnicode) { using (PinnedByteArray pData = MarshalStringToIntPtr(value)) { P4Bridge.set_passwordW(pServer, pData); } } else { P4Bridge.set_passwordA(pServer, value); } } } /// /// The program name used by the connection /// /// /// The program name and version are recorded in the server logs when /// accessed by the client /// public String ProgramName { get { IntPtr pval = P4Bridge.get_programName(pServer); return MarshalPtrToString(pval); } set { _prog_name = value; if (isUnicode) { using (PinnedByteArray pData = MarshalStringToIntPtr(_prog_name)) { P4Bridge.set_programNameW(pServer, pData); } } else { P4Bridge.set_programNameA(pServer, _prog_name); } } } /// /// The program version used by the connection /// /// /// The program name and version are recorded in the server logs when /// accessed by the client /// public String ProgramVersion { get { IntPtr pval = P4Bridge.get_programVer(pServer); return MarshalPtrToString(pval); } set { _prog_ver = value; if (isUnicode) { using (PinnedByteArray pData = MarshalStringToIntPtr(_prog_ver)) { P4Bridge.set_programVerW(pServer, pData); } } else { P4Bridge.set_programVerA(pServer, _prog_ver); } } } /// /// The current working directory (cwd) used by the p4 server /// /// /// The properties, client, port, user, and password, /// represent the criteria used to connect to a P4 server. If one or /// more is changed, the bridge will drop the current connection if any /// and attempt to connect to the (possibly different) P4 server when /// the next command is executed. If it is desirable to validate the /// connection, execute a command. /// public String CurrentWorkingDirectory { get { IntPtr pval = P4Bridge.get_cwd(pServer); return MarshalPtrToString(pval); } set { try { _cwd = value; if (isUnicode) { using (PinnedByteArray pData = MarshalStringToIntPtr(_cwd)) { P4Bridge.set_cwdW(pServer, pData); } } else { P4Bridge.set_cwdA(pServer, _cwd); } } catch (Exception ex) { LogFile.LogException("P4Bridge Error", ex); } } } /// /// The character set used by the connection /// /// /// The character set used to connect to Unicode servers is set by the /// bridge dll automatically (possibly overridden by P4CHARSET) based /// on the current Windows code page. /// public String CharacterSet { get { IntPtr pval = P4Bridge.get_charset(pServer); return Marshal.PtrToStringAnsi(pval); } } /// /// The config file used by the connection /// public String Config { get { IntPtr pval = P4Bridge.get_config(pServer); return MarshalPtrToString(pval); } } /// /// The config file that will be used by a given directory /// static public String GetConfig(string cwd) { IntPtr pval = IntPtr.Zero; using (PinnedByteArray pData = MarshalStringToIntPtr(Encoding.UTF8, cwd)) { pval = P4Bridge.get_configW(pData); } if (pval != IntPtr.Zero) { string val = MarshalPtrToStringUtf8_Int(pval); P4Bridge.Release(pval); return val; } return null; } /// /// Get an environment setting used by the server, such as user, client, .. /// /// The name of the environment varible /// static public String Get(string var) { IntPtr pval = IntPtr.Zero; using (PinnedByteArray pData = MarshalStringToIntPtr(Encoding.UTF8, var)) { pval = P4Bridge.GetW(pData); } if (pval != IntPtr.Zero) { string val = MarshalPtrToStringUtf8_Int(pval); P4Bridge.Release(pval); return val; } return null; } /// /// Set an environment setting used by the server, such as user, client, .. /// /// The name of the environment variable /// The new value for the environment variable /// static public void Set(string var, string val) { using (PinnedByteArray pData1 = MarshalStringToIntPtr(Encoding.UTF8, var), pData2 = MarshalStringToIntPtr(Encoding.UTF8, val)) { P4Bridge.SetW(pData1, pData2); } } /// /// Use the C++ API to determine if a file will be ignored /// /// The local path of the file /// true if the file will be ignored static public bool IsIgnored(string path) { using (PinnedByteArray pData = MarshalStringToIntPtr(Encoding.UTF8, path)) { return P4Bridge.IsIgnoredW(pData); } } } }