P4Connect.VerifySettings.cs #1

  • //
  • guest/
  • cswiedler/
  • p4connect/
  • main/
  • src/
  • P4Connect/
  • P4Connect/
  • P4Connect.VerifySettings.cs
  • View
  • Commits
  • Open Download .zip Download (28 KB)
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
{
    /// <summary>
    /// A collection of static methods that allow us to check the perforce connection settings
    /// </summary>
    public class VerifySettings
    {
        private static readonly ILog log = LogManager.GetLogger(typeof (VerifySettings));

        public static string LastWorkspaceMapping;

        private static readonly string fakeuser = "p4connect";

        /// <summary>
        /// If rv is true.  Server is valid
        /// If rv is false. Message is valid
        /// </summary>
        /// <param name="rv">pass / fail</param>
        /// <param name="cfg">ConnectionConfig reference</param>
        /// <param name="message">error message</param>
        public delegate void ServerInfoResult(bool rv, ConnectionConfig cfg, string message);

        public delegate void TestResult(bool rv, string message);

        /// <summary>
        /// Checks whether the source control setting is correct
        /// </summary>
        /// <returns></returns>
        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);
            }
        }

        /// <summary>
        /// Checks that the server is reachable
        /// Does not need a user, password or client
        /// </summary>
        /// <param name="cfg">Connection configuration</param>
        /// <param name="handler">callback handler</param>
        /// <returns>true if successful</returns>
        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;
		}

        /// <summary>
        /// Get a list of Users from the server
        /// Does not need a user, password or client
        /// </summary>
        /// <param name="cfg">Connection configuration</param>
        /// <param name="handler">callback handler</param>
        /// <returns>true if successful</returns>
        public static IList<User> GetUsers(ConnectionConfig cfg, TestResult handler = null)
        {
            IList<User> 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;
        }

        /// <summary>
        /// Get User from the server given username
        /// </summary>
        /// <param name="cfg">connection configuration</param>
        /// <param name="username">user name</param>
        /// <param name="handler">callback handler</param>
        /// <returns>User record for user specified</returns>
        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;
        }

        /// <summary>
        /// Save a User to the server given servername, user
        /// </summary>
        /// <param name="cfg">Connection configuration</param>
        /// <param name="user">User class reference</param>
        /// <param name="handler">callback handler</param>
        /// <returns>returns User record of user just saved</returns>
        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;
        }

        /// <summary>
        /// See if a specific user already exists on this server
        /// </summary>
        /// <param name="cfg">Connection configuration</param>
        /// <param name="username">user name</param>
        /// <returns>true if the user already exists on the server</returns>
        public static bool UserExists(ConnectionConfig cfg, string username)
        {
            IList<User> users = new List<User>();
            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));
        }

        /// <summary>
        /// Query the server for a list of workspaces available to "user"
        /// Must be wrapped in a "try" block
        /// </summary>
        /// <param name="cfg">Connection configuration</param>
        /// <param name="user">username to look up</param>
        /// <param name="handler">callback handler</param>
        /// <returns>an IList of Client</returns>
        public static IList<Client> GetWorkspacesByUser(ConnectionConfig cfg, string user, TestResult handler = null)
        {
           // Debug.Log("Getting workspaces from " + cfg.Server + " for user: "+ user);
            IList<Client> 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");
        }

        /// <summary>
        /// See if a specific workspace already exists on this server
        /// </summary>
        /// <param name="cfg">connection configuration</param>
        /// <param name="workspace">workspace name</param>
        /// <returns>true if specified workspace exists on the server</returns>
        public static bool WorkspaceExists(ConnectionConfig cfg, string workspace)
        {
            // Get the list of all Workspaces 
            var clients = new List<Client>();
            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));
        }

        /// <summary>
        /// Retrieve a workspace from the server as username
        /// given a workspace name
        /// </summary>
        /// <param name="cfg">Connection Configuration</param>
        /// <param name="workspace">name of workspace</param>
        /// <param name="handler">callback handler</param>
        /// <returns>Client Workspace record requested</returns>
        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;
        }


        /// <summary>
        /// Save a Workspace to given server as user
        ///   This is used to update server with modified or new user
        /// </summary>
        /// <param name="cfg">connection configuration</param>
        /// <param name="workspace">workspace to write</param>
        /// <param name="handler">callback</param>
        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");
        }

        /// <summary>
        /// Check if username and password are valid
        /// Uses ConnectionConfig settings for connection
        /// </summary>
        /// <param name="cfg" >Connection Configuration</param>
        /// <returns>true if successful</returns>
        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;
		}

		/// <summary>
		/// Check that the workspace is valid
		/// </summary>
		/// <parm name="cfg">Connection Configuration</parm>
		/// <returns>true if workspace is valid</returns>
		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;
		}

		/// <summary>
		/// Verifies that the project root is valid
		/// </summary>
		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;
		}
	
        /// <summary>
        /// Check P4Exceptions as they are recieved.  look for Password Errors so we can invoke the Password Dialog
        /// <param name="p4Ex">The Exception thrown</param>
        /// <param name="cfg">Connection Configuration</param>
       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);
            }
        }

        /// <summary>
        /// Return a string describing the components of a P4Error
        /// </summary>
        /// <param name="errorCode"></param>
        /// <returns>string description of error</returns>
        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());
        }
    }

}
# Change User Description Committed
#1 21852 cswiedler Branch //guest/cswiedler/p4connect
//guest/perforce_software/p4connect/main/src/P4Connect/P4Connect/P4Connect.VerifySettings.cs
#6 20275 Norman Morse Update source to match 2016.2 patch 2 release

Much thrashing of source during refactor.

Proper caching of asset statuses, reducing fstats issued.

Some bug fixes by Liz Lam

Cleanup of Pending changes,  rework of initialization and play/stop play transitions
#5 19279 Norman Morse Update workshop from internal changes.
These Changes are the basis of the 2016.2 release
- Dropped support for Unity 4
- Built in Unity 5
- Fixed assembly misidentifications.
- Fixed problem where configuration dialog would pop up between runs
#4 18942 Norman Morse 2016.1 Patch 2 changes
#3 18665 Norman Morse 16.2 preparation checkin
#2 18418 Norman Morse Many changes from the dev branch.
Icons are packaged differently.
Style Sheet is created / used

New ConnectionWizard Dialog
New PasswordDialog

Generate -a tickets

Update version to 2016.1.0.0
#1 16209 Norman Morse Move entire source tree into "main" branch so workshop code will act correctly.
//guest/perforce_software/p4connect/src/P4Connect/P4Connect/P4Connect.VerifySettings.cs
#8 15244 Norman Morse Better Directory support in "add" "get latest" "refresh" and other commands.
Improved Project Root detection
Various Bug Fixes and Clean up
#7 12568 Norman Morse Fixed some error handling during Perforce Configuration
#6 12565 Norman Morse Integrated from Dev Branch
Made ChangeManager into Static Class.
Improved close window behavior for when connection is invalid
Fixed localOpenFiles not updating on submit
#5 12553 Norman Morse integrate from internal main
Build fixes for EC.
Major changes to Configuration and re-initialization code.
Bug fixes
#4 12512 Norman Morse Integrate from Dev branch, preparing for Beta3 release
#3 12251 Norman Morse Fixes for Beta 2 release
Mostly Configuration dialog bug fixes
#2 12135 Norman Morse Integrate dev branch changes into main.

This code is the basiis of the 2.7 BETA release which provides Unity 5 compatibility
#1 10940 Norman Morse Inital Workshop release of P4Connect.
Released under BSD-2 license