using UnityEditor; using UnityEngine; using System; using System.Collections.Generic; using System.ComponentModel.Design; using System.Data.Sql; using System.IO; using System.Linq; using System.Text; using System.Xml.Serialization; using Perforce.P4; using log4net; namespace P4Connect { /// /// A collection of static methods that allow us to check the perforce connection settings /// public class VerifySettings { private static readonly ILog log = LogManager.GetLogger(typeof (VerifySettings)); public static string LastWorkspaceMapping; private static readonly string fakeuser = "p4connect"; private static readonly XmlSerializer ConnectionSerializer = new XmlSerializer(typeof(ConnectionConfig)); [Serializable] public class ConnectionConfig : IEquatable { public bool Tested { get; set; } public string Server { get; set; } public bool IsCaseSensitive { get; set; } public bool IsUnicode { get; set; } public int ApiLevel { get; set; } public bool RequiresLogin { get; set; } public bool ServerValid { get; set; } public string User { get; set; } public bool UserValid { get; set; } public string Password { get; set; } public bool PasswordValid { get; set; } public bool PasswordChanged { get; set; } public string Workspace { get; set; } public bool WorkspaceValid { get; set; } public string Hostname { get; set; } public string Charset { get; set; } public bool MetafilesValid { get; set; } public bool ProjectRootValid { get; set; } public override string ToString() { string svr = "Server: " + Server + " " + (ServerValid ? "valid" : "invalid") + "\n"; string usr = "User: " + User + " " + (UserValid ? "valid" : "invalid") + "\n"; string pwd = "Password: " + Password + " " + (PasswordValid ? "valid" : "invalid") + " " + (PasswordChanged ? "changed" : "unchanged") + "\n"; string wsp = "Workspace: " + Workspace + " " + (WorkspaceValid ? "valid" : "invalid") + "\n"; string msc = "Hostname: " + Hostname + " " + "Charset: " + Charset + "\n"; return svr + usr + pwd + wsp + msc; } /// /// Default Constructor /// public ConnectionConfig() { Reset(); } /// /// Copy Constructor /// /// public ConnectionConfig(ConnectionConfig other) { Server = other.Server; ServerValid = other.ServerValid; User = other.User; UserValid = other.UserValid; Password = other.Password; PasswordValid = other.PasswordValid; Workspace = other.Workspace; WorkspaceValid = other.WorkspaceValid; Hostname = other.Hostname; Charset = other.Charset; MetafilesValid = other.MetafilesValid; ProjectRootValid = other.ProjectRootValid; IsCaseSensitive = other.IsCaseSensitive; IsUnicode = other.IsUnicode; ApiLevel = other.ApiLevel; RequiresLogin = other.RequiresLogin; Tested = other.Tested; } public ConnectionConfig(bool bConfig ) { this.FromConfig(); } public string ToXml() { StringWriter myWriter = new StringWriter(); ConnectionSerializer.Serialize(myWriter, this); return myWriter.ToString(); } public bool Equals(ConnectionConfig other) { if (other == null) return false; if (other == this) return true; string myxml = ToXml(); string otherxml = other.ToXml(); bool rv = string.Equals(myxml, otherxml); if (!rv) { Debug.Log("Equals 1:" + myxml); Debug.Log("Equals 2:" + otherxml); } return rv; } public bool MatchesConfig() { if (!string.Equals(Server, Config.ServerURI)) return false; if (!string.Equals(User, Config.Username)) return false; if (!string.Equals(Password, Config.Password)) return false; if (!string.Equals(Workspace, Config.Workspace)) return false; if (!string.Equals(Hostname, Config.Hostname)) return false; if (!string.Equals(Charset, Config.Charset)) return false; return true; } /// /// Copy ConnectionConfig Information into Config Variables /// public void ToConfig() { Config.ServerURI = Server; Config.Username = User; Config.Password = Password; Config.Workspace = Workspace; Config.Hostname = Hostname; Config.Charset = Charset; } /// /// Copy Config Variables into ConnectionConfig /// public void FromConfig() { Server = Config.ServerURI; User = Config.Username; Password = Config.Password; Workspace = Config.Workspace; Hostname = Config.Hostname; Charset = Config.Charset; Reset(); } public void Reset() { MetafilesValid = ServerValid = UserValid = PasswordValid = WorkspaceValid = ProjectRootValid = false; IsCaseSensitive = IsUnicode = RequiresLogin = PasswordChanged = false; ApiLevel = 0; Tested = false; } public string Summary() { if (!Tested) return "Untested"; if (Valid) return "Connection Valid"; if (!MetafilesValid) return "Metafiles Misconfigured"; if (!ServerValid) return "Server invalid"; if (!UserValid) return "User invalid"; if (!PasswordValid) return "Password invalid"; if (!WorkspaceValid) return "Workspace invalid"; if (!ProjectRootValid) return "Project root invalid"; return ("unknown"); } public bool Valid { get { return MetafilesValid && ServerValid && UserValid && WorkspaceValid && PasswordValid && ProjectRootValid; } } } /// /// If rv is true. Server is valid /// If rv is false. Message is valid /// /// pass / fail /// ConnectionConfig reference /// error message public delegate void ServerInfoResult(bool rv, ConnectionConfig cfg, string message); public delegate void TestResult(bool rv, string message); /// /// Checks whether the source control setting is correct /// /// public static bool CheckMetaFiles() { return EditorSettings.externalVersionControl.Contains("Meta Files"); } /// /// Checks that the server is reachable /// Does not need a user, password or client /// /// Connection configuration /// callback handler /// true if successful public static bool CheckServerUri(ConnectionConfig cfg, ServerInfoResult handler = null) { bool serverValid = false; bool trustedServer = false; if (cfg.Server.Length == 0) { cfg.ServerValid = false; if (handler != null) { handler(serverValid, cfg, "Server address cannot be empty"); } return serverValid; } string[] pieces = cfg.Server.Split(':'); if (pieces.Count() < 2) { cfg.ServerValid = false; if (handler != null) { handler(serverValid, cfg, "Server address needs both hostname and port, with a colon between them"); } return serverValid; } retry_connection: Repository depot = null; // IDisposable try { Server testServer = new Server(new ServerAddress(cfg.Server)); depot = new Repository(testServer); Connection con = depot.Connection; con.UserName = fakeuser; con.Client = null; if (!String.IsNullOrEmpty(cfg.Hostname)) { System.Environment.SetEnvironmentVariable("P4HOST", cfg.Hostname); } bool rv = false; if (trustedServer) { rv = con.TrustAndConnect(Version.ConnectOptions, "-y", ""); } else { rv = con.Connect(Version.ConnectOptions); } //Debug.Log("server state: " + testServer.State.ToString()); // If that didn't throw an exception, make sure the server is reported as online serverValid = (testServer.State == ServerState.Online); cfg.ServerValid = serverValid; if (serverValid) { cfg.ApiLevel = con.ApiLevel; cfg.IsCaseSensitive = testServer.Metadata.CaseSensitive; cfg.IsUnicode = testServer.Metadata.UnicodeEnabled; } if (handler != null) { handler(serverValid, cfg, serverValid ? "Connected" : "Connection Failed"); } con.Disconnect(); } catch (P4Exception ex) { if (ex.ErrorCode == P4ClientError.MsgRpc_HostKeyUnknown) { // Unknown trust certificate... if (PromptForTrust(ex.Message)) { trustedServer = true; goto retry_connection; } } CheckP4Exception(ex, cfg); serverValid = false; if (handler != null) { handler(serverValid, cfg, ex.Message); } } finally { if (depot != null) depot.Dispose(); } System.Environment.SetEnvironmentVariable("P4HOST", ""); return serverValid; } /// /// Get a list of Users from the server /// Does not need a user, password or client /// /// Connection configuration /// callback handler /// true if successful public static IList GetUsers(ConnectionConfig cfg, TestResult handler = null) { IList users = null; Repository depot = null; // IDisposable try { Server testServer = new Server(new ServerAddress(cfg.Server)); depot = new Repository(testServer); Connection con = depot.Connection; con.UserName = String.Empty; con.Client = null; if (con.Connect(Version.ConnectOptions)) { //con.Trust(new TrustCmdOptions(TrustCmdFlags.AutoAccept), ""); //con.Credential = con.Login(cfg.Password); //cfg.PasswordValid = (con.Credential != null); UsersCmdOptions opts = new UsersCmdOptions(Perforce.P4.UsersCmdFlags.None, 20); users = depot.GetUsers(opts, null); } } catch (P4Exception ex) { CheckP4Exception(ex, cfg); if (handler != null) { handler(false, ex.Message); } } finally { if (depot != null) depot.Dispose(); } return users; } /// /// Get User from the server given username /// /// connection configuration /// user name /// callback handler /// User record for user specified public static User GetUser(ConnectionConfig cfg, string username, TestResult handler = null) { User user = null; Repository depot = null; // IDisposable try { Server testServer = new Server(new ServerAddress(cfg.Server)); depot = new Repository(testServer); Connection con = depot.Connection; con.UserName = cfg.User; if (con.Connect(Version.ConnectOptions)) { //con.Trust(new TrustCmdOptions(TrustCmdFlags.AutoAccept), ""); con.Credential = con.Login(cfg.Password, true); cfg.PasswordValid = (con.Credential != null); var opts = new UserCmdOptions(Perforce.P4.UserCmdFlags.Output); user = depot.GetUser(username,opts); } } catch (P4Exception ex) { CheckP4Exception(ex, cfg); if (handler != null) { handler(false, ex.Message); } } finally { if (depot != null) depot.Dispose(); } return user; } /// /// Save a User to the server given servername, user /// /// Connection configuration /// User class reference /// callback handler /// returns User record of user just saved public static User SaveUser(ConnectionConfig cfg, User user, TestResult handler = null) { Repository depot = null; // IDisposable try { Server testServer = new Server(new ServerAddress(cfg.Server)); depot = new Repository(testServer); Connection con = depot.Connection; con.UserName = cfg.User; if (con.Connect(Version.ConnectOptions)) { //con.Trust(new TrustCmdOptions(TrustCmdFlags.AutoAccept), ""); con.Credential = con.Login(cfg.Password, true); cfg.PasswordValid = (con.Credential != null); user = depot.CreateUser(user); } } catch (P4Exception ex) { CheckP4Exception(ex, cfg); if (handler != null) { handler(false, ex.Message); } } finally { if (depot != null) depot.Dispose(); } return user; } /// /// See if a specific user already exists on this server /// /// Connection configuration /// user name /// true if the user already exists on the server public static bool UserExists(ConnectionConfig cfg, string username) { IList users = new List(); try { users = VerifySettings.GetUsers(cfg, UserBrowseResponse); } catch (Exception ex) { Debug.Log("GetUsers Exception: " + ex.Message); } if (!users.Any()) { Debug.Log("no users found"); return false; } return users.Any(u => string.Equals(u.Id, username, cfg.IsCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); } /// /// Query the server for a list of workspaces available to "user" /// Must be wrapped in a "try" block /// /// Connection configuration /// username to look up /// callback handler /// an IList of Client public static IList GetWorkspacesByUser(ConnectionConfig cfg, string user, TestResult handler = null) { // Debug.Log("Getting workspaces from " + cfg.Server + " for user: "+ user); IList clients = null; Repository depot = null; // IDisposable try { Server testServer = new Server(new ServerAddress(cfg.Server)); depot = new Repository(testServer); Connection con = depot.Connection; con.UserName = cfg.User; if (con.Connect(Version.ConnectOptions)) { con.Credential = con.Login(cfg.Password, true); cfg.PasswordValid = (con.Credential != null); if (cfg.PasswordValid) { ClientsCmdOptions opts = new ClientsCmdOptions(Perforce.P4.ClientsCmdFlags.None, user, "", 20, ""); // Debug.Log("Clients options: " + opts.ToString()); clients = depot.GetClients(opts); } } } catch (P4Exception ex) { CheckP4Exception(ex, cfg); if (handler != null) { handler(false, ex.Message); } } finally { if (depot != null) depot.Dispose(); } return clients; } public static void UserBrowseResponse(bool result, string info) { string summary = result ? "Users Retrieved" : "Failed to retrieve users"; EditorUtility.DisplayDialog(summary, info, "ok"); } public static void WorkspaceBrowseResponse(bool result, string info) { string summary = result ? "Workspaces Retrieved" : "Failed to retrieve workspaces"; EditorUtility.DisplayDialog(summary, info, "ok"); } /// /// See if a specific workspace already exists on this server /// /// connection configuration /// workspace name /// true if specified workspace exists on the server public static bool WorkspaceExists(ConnectionConfig cfg, string workspace) { // Get the list of all Workspaces var clients = new List(); try { clients = GetWorkspacesByUser(cfg, "", WorkspaceBrowseResponse).ToList(); } catch (Exception ex) { Debug.Log("GetClients Exception: " + ex.Message); } if (!clients.Any()) { return false; } return clients.Any(c => string.Equals(c.Name, workspace, cfg.IsCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); } /// /// Retrieve a workspace from the server as username /// given a workspace name /// /// Connection Configuration /// name of workspace /// callback handler /// Client Workspace record requested public static Client GetWorkspace(ConnectionConfig cfg, string workspace, TestResult handler = null) { Client client = null; Repository depot = null; // IDisposable try { Server testServer = new Server(new ServerAddress(cfg.Server)); depot = new Repository(testServer); Connection con = depot.Connection; if (con.Connect(Version.ConnectOptions)) { var opts = new ClientCmdOptions(Perforce.P4.ClientCmdFlags.Output); client = depot.GetClient(workspace, opts); } } catch (P4Exception ex) { CheckP4Exception(ex, cfg); if (handler != null) { handler(false, ex.Message); } } finally { if (depot != null) depot.Dispose(); } return client; } /// /// Save a Workspace to given server as user /// This is used to update server with modified or new user /// /// connection configuration /// workspace to write /// callback public static void SaveWorkspace(ConnectionConfig cfg, Client workspace, TestResult handler = null) { Repository depot = null; // IDisposable try { Server testServer = new Server(new ServerAddress(cfg.Server)); depot = new Repository(testServer); Connection con = depot.Connection; con.UserName = cfg.User; if (con.Connect(Version.ConnectOptions)) { depot.CreateClient(workspace); } } catch (P4Exception ex) { CheckP4Exception(ex, cfg); if (handler != null) { handler(false, ex.Message); } } finally { if (depot != null) depot.Dispose(); } } public static void PromptForPassword() { PasswordPrompt.Init(); } public static bool PromptForTrust(string trustKey) { bool rv = EditorUtility.DisplayDialog("Untrusted Server!", trustKey, "Trust Server", "Cancel"); if (rv == true) { } return rv; } /// /// Check if username and password are valid /// Uses ConnectionConfig settings for connection /// /// Connection Configuration /// true if successful public static bool CheckUsernamePassword(ConnectionConfig cfg) { if (PasswordPrompt.Active) { return false; } // Debug.Log("CheckUsernamePassword :" + cfg.ToString()); Repository depot = null; // IDisposable try { Server testServer = new Server(new ServerAddress(cfg.Server)); depot = new Repository(testServer); Connection con = depot.Connection; if (!String.IsNullOrEmpty(cfg.Hostname)) { System.Environment.SetEnvironmentVariable("P4HOST", cfg.Hostname); } if (!String.IsNullOrEmpty(cfg.Charset)) { System.Environment.SetEnvironmentVariable("P4CHARSET", cfg.Charset); } con.UserName = cfg.User; // Attempt to open a secure connection with the given password if (con.Connect(Version.ConnectOptions)) { cfg.UserValid = true; Credential credentials = con.Login(cfg.Password, true); cfg.PasswordValid = (credentials != null); } con.Disconnect(); } catch (P4Exception ex) { CheckP4Exception(ex, cfg); cfg.PasswordValid = false; } finally { if (depot != null) depot.Dispose(); } System.Environment.SetEnvironmentVariable("P4HOST", ""); System.Environment.SetEnvironmentVariable("P4CHARSET", ""); return cfg.PasswordValid; } /// /// Check that the workspace is valid /// /// Connection Configuration /// true if workspace is valid public static bool CheckWorkspace(ConnectionConfig cfg) { if (PasswordPrompt.Active) { return false; } // System.Console.WriteLine("CheckWorkspace()"); if (String.IsNullOrEmpty(cfg.Workspace)) return false; cfg.WorkspaceValid = false; Repository depot = null; // IDisposable try { // Open a connection, assume a secure one, won't hurt if it's not needed Server testServer = new Server(new ServerAddress(cfg.Server)); depot = new Repository(testServer); Connection con = depot.Connection; if (!String.IsNullOrEmpty(cfg.Hostname)) { System.Environment.SetEnvironmentVariable("P4HOST", cfg.Hostname); } if (!String.IsNullOrEmpty(cfg.Charset)) { System.Environment.SetEnvironmentVariable("P4CHARSET", cfg.Charset); } con.UserName = cfg.User; // Open the connection and try to get the list of opened files on the workspace con.Client = new Client(); con.Client.Name = cfg.Workspace; con.Connect(Version.ConnectOptions); //con.Trust(new TrustCmdOptions(TrustCmdFlags.AutoAccept), ""); Credential credentials = con.Login(cfg.Password, true); con.Credential = credentials; // Try to get the list of opened files, it'll throw an exception if workspace is invalid depot.GetOpenedFiles(null, null); con.Disconnect(); cfg.WorkspaceValid = true; } catch (P4Exception p4Ex) { CheckP4Exception(p4Ex, cfg); cfg.WorkspaceValid = false; } finally { depot.Dispose(); } System.Environment.SetEnvironmentVariable("P4HOST", ""); System.Environment.SetEnvironmentVariable("P4CHARSET", ""); return cfg.WorkspaceValid; } /// /// Verifies that the project root is valid /// public static bool CheckProjectRoot(ConnectionConfig cfg) { Repository depot = null; // IDisposable bool rootValid = false; try { // Open a connection, assume a secure one, won't hurt if it's not needed Server testServer = new Server(new ServerAddress(cfg.Server)); depot = new Repository(testServer); Connection con = depot.Connection; if (!string.IsNullOrEmpty(cfg.Hostname)) { System.Environment.SetEnvironmentVariable("P4HOST", cfg.Hostname); } if (!string.IsNullOrEmpty(cfg.Charset)) { System.Environment.SetEnvironmentVariable("P4CHARSET", cfg.Charset); } con.UserName = cfg.User; Client myclient = new Client(); myclient.Name = cfg.Workspace; con.Client = myclient; if (con.Connect(Version.ConnectOptions)) { cfg.ServerValid = true; //con.Trust(new TrustCmdOptions(TrustCmdFlags.AutoAccept), ""); Credential credentials = con.Login(cfg.Password, true); con.Credential = credentials; // Run "p4 client" to get workspace information ClientMetadata metaData = depot.GetClientMetadata(); if (metaData != null && metaData.Root != null) { LastWorkspaceMapping = metaData.Root.Replace('/', System.IO.Path.DirectorySeparatorChar); rootValid = Utils.IsDirOrValidSubDirectoryOf(Main.RootPath, LastWorkspaceMapping); } // Run "p4 where" to get ClientProjectRoot var spec = FileSpec.LocalSpec(System.IO.Path.Combine(Main.RootPath, "...")); var mappings = con.Client.GetClientFileMappings(spec); if (mappings != null && mappings.Count > 0) { Config.ClientProjectRoot = mappings[0].ClientPath.Path; Config.DepotProjectRoot = mappings[0].DepotPath.Path; //log.Debug("ClientProjectRoot: " + Config.ClientProjectRoot); //log.Debug("DepotProjectRoot: " + Config.DepotProjectRoot); } else { Debug.LogError("Unable to determine Project Root! "); } con.Disconnect(); } } catch (P4Exception p4Ex) { CheckP4Exception(p4Ex, cfg); rootValid = false; } catch (Exception ex) { Debug.LogException(ex); rootValid = false; } finally { depot.Dispose(); } System.Environment.SetEnvironmentVariable("P4HOST", ""); System.Environment.SetEnvironmentVariable("P4CHARSET", ""); cfg.ProjectRootValid = rootValid; return rootValid; } /// /// Check P4Exceptions as they are recieved. look for Password Errors so we can invoke the Password Dialog /// The Exception thrown /// Connection Configuration static void CheckP4Exception(P4Exception p4Ex, ConnectionConfig cfg) { if (p4Ex.ErrorCode == P4ClientError.MsgServer_BadPassword || p4Ex.ErrorCode == P4ClientError.MsgServer_BadPassword0 || p4Ex.ErrorCode == P4ClientError.MsgServer_BadPassword1) { if (!PasswordPrompt.Active) { PromptForPassword(); } } else { Debug.Log("Unhandled P4Exception: " + p4Ex.ErrorCode + " msg: " + p4Ex.Message); Debug.Log(P4ErrorToString(p4Ex.ErrorCode)); Debug.LogException(p4Ex); } } /// /// Return a string describing the components of a P4Error /// /// /// string description of error static string P4ErrorToString(int errorCode) { int subcode = unchecked(errorCode & 0x3ff); int unique = unchecked(errorCode & 0xffff); int argcount = unchecked((errorCode >> 24) & 0x0f); ErrorGeneric gen = (ErrorGeneric)unchecked((errorCode >> 16) & 0xff); ErrorSubsystem subsys = (ErrorSubsystem)unchecked((errorCode >> 10) & 0x3f); ErrorSeverity sev = (ErrorSeverity)unchecked((errorCode >> 28) & 0x0f); return ("P4Error: subsystem: " + subsys.ToString() + " code: " + subcode.ToString() + " unique: " + unique.ToString() + " generic: " + gen.ToString() + " severity: " + sev.ToString()); } } }