using UnityEditor; using UnityEngine; using System; using System.Collections.Generic; 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"; /// /// 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"); } // Delegate Echos Perforce commands to the p4connect log static void CommandEcho(String data) { if (Config.EchoP4Commands) { log.Debug(data); } } /// /// 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; 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; } 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 = con.Connect(Version.ConnectOptions); con.CommandEcho += CommandEcho; // 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) { //Debug.Log("SERVERVALID in CheckServerURI"); 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.CommandEcho -= CommandEcho; con.Disconnect(); } catch (P4Exception ex) { if (ex.ErrorCode == P4ClientError.MsgRpc_HostKeyUnknown) { //Debug.Log("Ignoring Trust Error in CheckServerURI"); serverValid = true; // ignore trust errors during URI test } CheckP4Exception(ex, cfg); 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.CommandEcho -= CommandEcho; UsersCmdOptions opts = new UsersCmdOptions(Perforce.P4.UsersCmdFlags.None, 20); users = depot.GetUsers(opts, null); con.CommandEcho -= CommandEcho; } } 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.CommandEcho += CommandEcho; 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); con.CommandEcho -= CommandEcho; } } 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.CommandEcho += CommandEcho; con.Credential = con.Login(cfg.Password, true); cfg.PasswordValid = (con.Credential != null); user = depot.CreateUser(user); con.CommandEcho -= CommandEcho; } } 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.CommandEcho += CommandEcho; 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, ""); clients = depot.GetClients(opts); } con.CommandEcho -= CommandEcho; } } 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, cfg.User, 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; con.UserName = cfg.User; if (con.Connect(Version.ConnectOptions)) { con.CommandEcho += CommandEcho; var opts = new ClientCmdOptions(Perforce.P4.ClientCmdFlags.Output); client = depot.GetClient(workspace, opts); con.CommandEcho -= CommandEcho; } } 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)) { con.CommandEcho += CommandEcho; depot.CreateClient(workspace); con.CommandEcho -= CommandEcho; } } 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) { return EditorUtility.DisplayDialog("Untrusted Server!", trustKey, "Trust This Server", "Cancel"); } /// /// 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()); bool trustedServer = false; retry_connection: Repository depot = null; // IDisposable try { bool rv = false; 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; if (trustedServer) { rv = con.TrustAndConnect(Version.ConnectOptions, "-y", ""); } else { rv = con.Connect(Version.ConnectOptions); } if (rv) { con.CommandEcho += CommandEcho; cfg.UserValid = true; Credential credentials = con.Login(cfg.Password, true); cfg.PasswordValid = (credentials != null); //// 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; //} con.CommandEcho -= CommandEcho; } 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); 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.CommandEcho += CommandEcho; 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.CommandEcho -= CommandEcho; 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)) { con.CommandEcho += CommandEcho; 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; Config.LocalProjectRoot = mappings[0].LocalPath.Path; //log.Debug("ClientProjectRoot: " + Config.ClientProjectRoot); //log.Debug("DepotProjectRoot: " + Config.DepotProjectRoot); log.Debug("LocalProjectRoot: " + Config.LocalProjectRoot); } else { Debug.LogError("Unable to determine Project Root! "); } con.CommandEcho -= CommandEcho; 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 if (p4Ex.ErrorCode == P4ClientError.MsgRpc_HostKeyUnknown) { // Ignore this. } 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()); } } }