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;
//log.Debug("ClientProjectRoot: " + Config.ClientProjectRoot);
//log.Debug("DepotProjectRoot: " + Config.DepotProjectRoot);
}
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());
}
}
}