// // Copyright 2014 Perforce Software Inc. // using log4net; using Perforce.Model; using Perforce.P4; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.Caching; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; using System.Windows; namespace Perforce.Helper { public class PerforceHelper : IDisposable { enum PerforceErrors { NoFilesToSubmit = 806427694, NoFilesToSubmitWithError = 822418474 } private string _serverUri; private string _username; private string _password; private string _ticket; private Client _client; private static readonly ILog _log = LogManager.GetLogger(typeof(PerforceHelper)); #region LOGIN AND CONNECTION TESTING /// /// Creates a connection to the Perforce server /// /// true if connected, false otherwise public bool Connect() { var connected = false; var con = GetConnection(); if (con.Status == ConnectionStatus.Disconnected) { try { con.Connect(null); } catch (P4Exception pe) { throw new P4HelperException(pe.Message); } } if (con.Status == ConnectionStatus.Connected) { connected = true; } return connected; } /// /// Disconnects from the Perforce server /// public void Disconnect() { var con = GetConnection(); if (con != null && con.Status == ConnectionStatus.Connected) { con.Disconnect(); } } /// /// Tests to see if the connection has a Connected status /// /// true if connected, false otherwise public bool IsConnected() { return GetRepository().Connection.Status == ConnectionStatus.Connected; } /// /// Logs into the Perforce server using the specified password /// /// Thrown if a P4Exception is caught public Tuple Login(string password) { var result = new Tuple(false, string.Empty); var con = GetConnection(useCache: false, testConnection: false); if (Connect()) { try { var options = new LoginCmdOptions(LoginCmdFlags.AllHosts, string.Empty); var cred = con.Login(password, options); if (cred != null) { if (string.IsNullOrEmpty(cred.Ticket)) { options = new LoginCmdOptions(LoginCmdFlags.AllHosts | LoginCmdFlags.DisplayTicket, string.Empty); cred = con.Login(password, options); } result = new Tuple(true, cred.Ticket); Ticket = cred.Ticket; } } catch (P4Exception pe) { throw new P4HelperException(pe.Message); } } return result; } /// /// Checks to see if the current session is logged in /// /// true if logged in, false otherwise public bool IsLoggedIn() { var result = false; var con = GetConnection(useCache: false, testConnection: false); if (con != null) { try { string[] args = { "-s" }; P4Command cmd = new P4Command(con, "login", false, args); var r = cmd.Run(); result = true; } catch (P4Exception) { // do nothing... an exception is thrown if login -s fails, indicating that // the user is not logged in } } return result; } /// /// Logs out the current session /// /// returns the result of the logout operation public bool Logout() { var result = false; var con = GetConnection(useCache: false, testConnection: false); if (con != null) { var options = new LogoutCmdOptions(LogoutCmdFlags.AllHosts, string.Empty); result = con.Logout(options); } return result; } #endregion #region SERVER OPERATIONS public long GetServerTime() { return Utility.GetEpochTime(GetServer().Metadata.Date.ToUniversalTime()); } public string GetKey(string name) { string value = null; if (string.IsNullOrEmpty(name)) return null; var args = new List(); args.Add(name); var cmd = new P4Command(GetRepository(), "key", true, args.ToArray()); var results = cmd.Run(); if (results.TaggedOutput != null) { foreach (TaggedObject obj in results.TaggedOutput) { if (obj.ContainsKey("value")) { var x = obj["value"].ToString(); if (!x.Equals("0")) { value = x; } } } } return value; } #endregion #region USER OPERATIONS public User GetUserInfo(string userid) { if (string.IsNullOrEmpty(userid)) return null; var repo = GetRepository(); return repo.GetUser(userid); } #endregion #region LISTING OPERATIONS public bool IsDirectory(string path) { if (path.Equals("//")) return true; if (path.EndsWith("/")) return true; if (path.EndsWith("/...")) return true; var repo = GetRepository(); string[] p = { path }; var options = new GetDepotDirsCmdOptions(GetDepotDirsCmdFlags.None, null); var results = repo.GetDepotDirs(options, p); if (results.Count == 1) { return true; } else { var clientPath = PredictClientPath(path); var exists = Directory.Exists(clientPath); return exists; } } public bool IsFile(string path) { var repo = GetRepository(); string[] p = { path }; var specs = GetFileSpecs(p); var options = new GetDepotFilesCmdOptions(GetDepotFilesCmdFlags.None, 0); var results = repo.GetDepotFiles(specs, options); return results.Count == 1; } /// /// Gets a list of the depots on the Perforce server /// /// optional boolean to limit depots to only those of type 'local' /// A list of Depot specifications public IList ListDepots(bool localOnly=false) { IList depots = null; var repo = GetRepository(); if (repo != null) { var allDepots = repo.GetDepots(); if (localOnly) { depots = allDepots.Where(d => d.Type == DepotType.Local).ToList(); } else { depots = allDepots; } } return depots; } /// /// Gets a list of directories given the specified depot path /// /// The depot path to use as the base /// A list of string objects public IList ListDirectories(string path) { var repo = GetRepository(); if (!path.EndsWith("/")) { path += "/"; } string[] p = { path + "*" }; var options = new GetDepotDirsCmdOptions(GetDepotDirsCmdFlags.None, null); return repo.GetDepotDirs(options, p); } /// /// Gets a list of the files at the specified depot path /// /// The depot path to use as the base /// A list of FileMetaData (fstat) objects public IList ListFiles(string depotPath, bool showDeleted=false, long sinceTime = 0) { var repo = GetRepository(); var filespec = new List(); filespec.Add(new FileSpec(new DepotPath(depotPath), Revision.Head)); string filter = string.Empty; if (!showDeleted) { filter = "^headAction=delete ^headAction=move/delete"; } if (sinceTime > 0) { if (filter.Length > 0) { filter += " & "; } filter += string.Format("headTime > {0}", sinceTime); } var options = new GetFileMetaDataCmdOptions(GetFileMetadataCmdFlags.Attributes|GetFileMetadataCmdFlags.FileSize|GetFileMetadataCmdFlags.HexAttributes, filter, null, -1, null, null, "tags"); return repo.GetFileMetaData(filespec, options); } #endregion #region CLIENT OPERATIONS /// /// Gets a list of the clients owned by the current user /// /// A list of Client objects public IList ListClients(bool validLocalOnly=false) { var repo = GetRepository(); var options = new ClientsCmdOptions(ClientsCmdFlags.None, _username, null, -1, null); var clients = repo.GetClients(options); if (clients != null && validLocalOnly) { var subset = new List(); foreach (var c in clients) { var pathRoot = Path.GetPathRoot(c.Root); if (!pathRoot.StartsWith("\\") && Directory.Exists(pathRoot)) { subset.Add(c); } } clients = subset; } return clients; } /// /// Creates a basic client /// /// The name of the client /// The root directory for the client /// A description for the client /// The client created public Client CreateClient(string name, string root, string description) { Client client = null; if (!ClientExists(name)) { client = new Client(); client.Name = name; client.OwnerName = _username; client.Options = ClientOption.RmDir|ClientOption.Clobber; client.LineEnd = LineEnd.Local; client.Description = description; client.Root = root; client.SubmitOptions = new ClientSubmitOptions(false, SubmitType.RevertUnchanged); client.ViewMap = new ViewMap(); } else { client = GetRepository().GetClient(name); client.Root = root; client.Description = description; } SaveClient(client); return client; } /// /// Creates a client based on the client specification /// /// The client specification to create /// The client created public Client SaveClient(Client client) { var result = GetRepository().CreateClient(client); CurrentClient = client; return CurrentClient; } public Client GetClient(string name) { return GetRepository().GetClient(name); } public Client UpdateClient(Client client) { GetRepository().UpdateClient(client); CurrentClient = client; return CurrentClient; } /// /// Checks to see if a particular client already exists on the Perforce server /// /// The name of the client specification to look for /// true if the client is found, false otherwise public bool ClientExists(string name) { bool found = false; var repo = GetRepository(); var options = new ClientsCmdOptions(ClientsCmdFlags.IgnoreCase, null, name, -1, null); var matching = repo.GetClients(options); if (matching != null && matching.Count > 0) { found = true; } return found; } public Client IncludeInClient(string depotPath) { Client c = GetClient(_client.Name); MapEntry line = CreateMapEntry(depotPath, _client.Name, MapType.Include); if (c.ViewMap == null) { c.ViewMap = new ViewMap(); } if (!c.ViewMap.Contains(line)) { // check to see if c.ViewMap.Add(line); } return UpdateClient(c); } public Client RemoveFromClient(string depotPath) { Client updated = null; Client c = GetClient(_client.Name); var entry = CreateMapEntry(depotPath, _client.Name, MapType.Include); var changed = false; if (c.ViewMap != null && c.ViewMap.Count > 0) { if (c.ViewMap.Contains(entry)) { c.ViewMap.Remove(entry); changed = true; } else { // need to iterate over the viewmap to see if this is a subdirectory // of an existing item var found = false; foreach (var mapping in c.ViewMap) { var left = mapping.Left.Path; if (left.EndsWith("/...")) { if (depotPath.StartsWith(left.TrimEnd('.'))) { found = true; break; } } } if (found) { var excludeEntry = CreateMapEntry(depotPath, _client.Name, MapType.Exclude); c.ViewMap.Add(excludeEntry); changed = true; } } } if (changed) { updated = UpdateClient(c); } return updated; } public MapEntry CreateMapEntry(string depotPath, string clientName, MapType type) { var left = new DepotPath(depotPath); var clientPath = string.Format("//{0}/{1}", clientName, depotPath.Substring(2)); var right = new ClientPath(clientPath); return new MapEntry(type, left, right); } /// /// Removes the client specification from the server /// /// The name of the client to delete public void DeleteClient(string name) { var repo = GetRepository(); var client = repo.GetClient(name); if (client != null) { var options = new DeleteFilesCmdOptions(DeleteFilesCmdFlags.None, -1); repo.DeleteClient(client, options); } } /// /// CurrentClient property /// public Client CurrentClient { get { return _client; } set { _client = value; SetClient(_client.Name) ; } } public void SetClient(string name) { GetConnection().SetClient(name); _client = GetClient(name); var needsUpdate = false; if (!_client.Options.HasFlag(ClientOption.RmDir)) { _client.Options = _client.Options | ClientOption.RmDir; needsUpdate = true; } if (!_client.Options.HasFlag(ClientOption.Clobber)) { _client.Options = _client.Options | ClientOption.Clobber; needsUpdate = true; } if(needsUpdate) { GetRepository().UpdateClient(_client); GetConnection().SetClient(name); _client = GetClient(name); } } #endregion #region CHANGELIST OPERATIONS /// /// Creates a new changelist /// /// The description for the changelist /// The changelist created public Changelist CreateChangelist(string description) { var change = new Changelist(); change.OwnerName = _username; change.ClientId = CurrentClient.Name; change.Description = description; var repo = GetRepository(); change = repo.CreateChangelist(change); return change; } /// /// Retrieves the numbered changelist /// /// The changelist Id number to retrieve /// The changelist specified by the Id number public Changelist GetChangelist(int id, bool includeShelvedFiles = false) { var repo = GetRepository(); Options opts = null; if (includeShelvedFiles) { var flags = DescribeChangelistCmdFlags.Shelved; opts = new DescribeCmdOptions(flags, 0, 0); } else { var flags = ChangeCmdFlags.None; opts = new ChangeCmdOptions(flags); } return repo.GetChangelist(id, opts); } public IList GetAllPendingChangelists() { var repo = GetRepository(); var options = new ChangesCmdOptions(ChangesCmdFlags.None, CurrentClient.Name, 0, ChangeListStatus.Pending, Username); return repo.GetChangelists(options, null); } public Changelist GetCurrentPendingChangelist(bool shelved=false) { Changelist current = null; var repo = GetRepository(); var options = new ChangesCmdOptions(ChangesCmdFlags.None, CurrentClient.Name, 1, ChangeListStatus.Pending, Username); var changes = repo.GetChangelists(options, null); if (changes != null && changes.Count == 1) { //current = changes[0]; var id = changes[0].Id; Options opts = null; if (shelved) { var flags = DescribeChangelistCmdFlags.Shelved; opts = new DescribeCmdOptions(flags, 0, 0); } else { var flags = ChangeCmdFlags.None; opts = new ChangeCmdOptions(flags); } current = repo.GetChangelist(id, opts); } return current; } public void DeletePendingChangeList() { var change = GetCurrentPendingChangelist(); if (change != null) { var repo = GetRepository(); var options = new Options(); repo.DeleteChangelist(change, options); } } // Cleans the current changelist, looking for any files that have been deleted locally since // the changelist was created. The basic idea of this method is to reconcile deletions on local // disk with the state of the changelist -- something that p4 reconcile doesn't find and // which causes issues when trying to submit a changelist public void CleanChangelist() { var change = GetCurrentPendingChangelist(); // only look at things if the changelist exists and there are files in it if (change != null && change.Files != null && change.Files.Count > 0) { var helper = Utility.GetPerforceHelper(); var filesToRevert = new List(); var filesToRemove = new List(); foreach (var f in change.Files) { if (f.Action == FileAction.Add) { // if the added file no longer exists, then we just need to revert var md = helper.GetFileMetaData(f.DepotPath.Path); if (!System.IO.File.Exists(md.LocalPath.Path)) { filesToRevert.Add(f.DepotPath.Path); } } else if (f.Action == FileAction.MoveAdd) { // if the renamed file does not exist, we will revert and then delete // the original var md = helper.GetFileMetaData(f.DepotPath.Path); if (!System.IO.File.Exists(md.LocalPath.Path)) { filesToRevert.Add(f.DepotPath.Path); filesToRemove.Add(md.MovedFile.Path); } } else if (f.Action == FileAction.Edit) { // we were editing the file, and now it has been deleted so we need to // mark it for delete var md = helper.GetFileMetaData(f.DepotPath.Path); if (!System.IO.File.Exists(md.LocalPath.Path)) { filesToRevert.Add(f.DepotPath.Path); filesToRemove.Add(f.DepotPath.Path); } } } // process any files that need to be reverted first if (filesToRevert.Count > 0) { var list = filesToRevert.ToArray(); helper.RevertFiles(serverOnly: false, paths: list); } // we only need to save the changelist if we actually cleaned stuff up if (filesToRemove.Count > 0) { var list = filesToRemove.ToArray(); helper.DeleteFiles(serverOnly: false, paths: list); } } } public IList GatherOpenFilesInCurrentChangelist() { IList reopenedFiles = null; var repo = GetRepository(); var currentChange = GetOrCreatePendingChangelist(); var openedSpecs = new List(); openedSpecs.Add(GetFileSpec("//...")); var openedOptions = new GetOpenedFilesOptions(GetOpenedFilesCmdFlags.None, null, CurrentClient.Name, Username, 0); var list = GetRepository().GetOpenedFiles(openedSpecs, openedOptions); var reopenSpecs = new List(); foreach (var i in list) { if (i.ChangeId != currentChange.Id) { reopenSpecs.Add(new FileSpec(i.DepotPath)); } } if (reopenSpecs.Count > 0) { var options = new ReopenCmdOptions(currentChange.Id, null); reopenedFiles = repo.Connection.Client.ReopenFiles(reopenSpecs, options); } return reopenedFiles; } public IList MoveFilesToNewChangelist(IList files, int changeId=0) { var repo = GetRepository(); if (!(changeId > 0)) { var change = CreateChangelist("Perforce-created changelist"); changeId = change.Id; } var options = new ReopenCmdOptions(changeId, null); var fileSpecs = new List(); foreach (var f in files) { if (f.StartsWith("//")) { fileSpecs.Add(new FileSpec(new DepotPath(f))); } else { fileSpecs.Add(new FileSpec(new ClientPath(f))); } } return repo.Connection.Client.ReopenFiles(fileSpecs, options); } /// /// Deletes the changelist /// /// The changelist Id number to delete public void DeleteChangelist(int id) { var change = GetChangelist(id); if (change != null && change.Pending) { DeleteChangelist(change); } } /// /// Deletes the changelist /// /// The changelist object to delete public void DeleteChangelist(Changelist change) { var repo = GetRepository(); var options = new ChangeCmdOptions(ChangeCmdFlags.Delete); repo.DeleteChangelist(change, options); } public IList ListChanges(string depotPath) { var repo = GetRepository(); if (IsDirectory(depotPath) && !depotPath.EndsWith("/...")) { depotPath = depotPath + "/..."; } var spec = GetFileSpec(depotPath); var options = new ChangesCmdOptions(ChangesCmdFlags.FullDescription, null, 50, ChangeListStatus.Submitted, null); return repo.GetChangelists(options, spec); } public ResultsWrapper SubmitSingleFile(string depotPath, string description) { var change = CreateChangelist(description); var reopenOptions = new ReopenCmdOptions(change.Id, null); var spec = GetFileSpec(depotPath); GetRepository().Connection.Client.ReopenFiles(reopenOptions, spec); return SubmitChangelist(change.Id, description, revertUnchanged: true); } public ResultsWrapper SubmitChangelist(int id, string description=null, bool revertUnchanged=true) { var change = GetChangelist(id); if (change != null && change.Pending) { if (description != null) { change.Description = description; } return SubmitChangelist(change, revertUnchanged); } else { return null; } } /// /// /// /// /// /// public ResultsWrapper SubmitChangelist(Changelist change=null, bool revertUnchanged=true) { var wrapper = new ResultsWrapper(); // get the current pending changelist if the changelist is not specified if (change == null) { change = GetCurrentPendingChangelist(); if (change == null) { return null; } } else { GetRepository().UpdateChangelist(change); } var clientSubmitOptions = new ClientSubmitOptions(); clientSubmitOptions.Reopen = false; if (revertUnchanged) { clientSubmitOptions.SubmitType = SubmitType.RevertUnchanged; } else { clientSubmitOptions.SubmitType = SubmitType.SubmitUnchanged; } var options = new SubmitCmdOptions(SubmitFilesCmdFlags.None, change.Id, null, null, clientSubmitOptions); try { wrapper.Results = GetConnection().Client.SubmitFiles(options, null); wrapper.HasError = false; wrapper.Message = "Submit successful"; } catch (P4Exception p4e) { wrapper.HasError = true; switch (p4e.ErrorCode) { case (int)PerforceErrors.NoFilesToSubmit: wrapper.HasError = false; wrapper.Message = "There were no changed files to submit"; break; case (int)PerforceErrors.NoFilesToSubmitWithError: if (p4e.Details.Count > 0) { var errorText = new StringBuilder(); foreach (var d in p4e.Details) { errorText.AppendLine(d.Message); } wrapper.Message = errorText.ToString(); } break; default: if (p4e.Details != null && p4e.Details.Count > 0) { var errorText = new StringBuilder(); foreach (var d in p4e.Details) { errorText.AppendLine(d.Message); } wrapper.Message = errorText.ToString(); } else if(p4e.Message != null) { wrapper.Message = p4e.Message; } break; } } return wrapper; } /// /// /// /// /// public IList GetChangelistFiles(Changelist change = null) { // get the current pending changelist if the changelist is not specified if (change == null) { change = GetCurrentPendingChangelist(); } if (change == null) { return null; } return change.Files; } public IList GetShelvedChangelistFiles(Changelist change = null) { if (change == null) { change = GetCurrentPendingChangelist(shelved: true); } if (change == null) { return null; } return change.ShelvedFiles; } /// /// /// /// public IList RevertChangelist(Changelist change=null) { // get the current pending changelist if the changelist is not specified if (change == null) { change = GetCurrentPendingChangelist(); } if (change == null) { return null; } var fileSpec = new FileSpec(new DepotPath("//...")); var options = new RevertCmdOptions(RevertFilesCmdFlags.None, change.Id); return GetRepository().Connection.Client.RevertFiles(options, fileSpec); } public Changelist GetOrCreatePendingChangelist() { var change = GetCurrentPendingChangelist(); if (change == null) { change = CreateChangelist(Constants.GENERATED_CHANGELIST_DESCRIPTION); } return change; } #endregion #region FILE OPERATIONS public bool PathExists(string depotPath) { var exists = false; // first check to see if the path is an existing file var specs = new List(); specs.Add(GetFileSpec(depotPath)); var fileOptions = new GetDepotFilesCmdOptions(GetDepotFilesCmdFlags.NotDeleted, 0); try { var fileResults = GetRepository().GetDepotFiles(specs, fileOptions); if (fileResults != null && fileResults.Count == 1) { exists = true; } } catch (P4Exception p4e) { // expected exception if the file does not exist } // if the file does not exist, check to see if it is a directory if (!exists) { var dirOptions = new GetDepotDirsCmdOptions(GetDepotDirsCmdFlags.None, null); try { var dirList = new List(); dirList.Add(depotPath); var dirResults = GetRepository().GetDepotDirs(dirList, dirOptions); if (dirResults != null && dirResults.Count == 1) { exists = true; } } catch (P4Exception p4e) { // expected exception if dir does not exist } } return exists; } public bool IsDirectoryMapped(string depotDir) { // normalize directory depotDir = depotDir.TrimEnd('/'); var list = new List(); list.Add(depotDir); var response = GetClientMappings(list, true); return response.Keys.Contains(depotDir); } public bool IsPathInClientView(string depotPath) { var inView = false; if (depotPath != null && GetConnection().Client != null && GetConnection().Client.ViewMap != null) { var map = GetConnection().Client.ViewMap; foreach (var e in map) { if (e.Left.Path.StartsWith(depotPath)) { inView = true; break; } } } return inView; } //** TODO: need to figure out why this is being called with invalid paths **// public Dictionary GetClientMappings(IList paths, bool pathsAreDirectories=false) { var mappings = new Dictionary(StringComparer.CurrentCultureIgnoreCase); if (paths != null && paths.Count > 0) { var fileArr = new List(); foreach (var path in paths) { var p = path; if (pathsAreDirectories) { p = string.Format("{0}/%", p); } fileArr.Add(p); } try { var cmdResults = WhereResults(fileArr.ToArray()); if (cmdResults.TaggedOutput != null) { foreach (TaggedObject obj in cmdResults.TaggedOutput) { if (!obj.ContainsKey("unmap")) { if (obj.ContainsKey("depotFile") && obj.ContainsKey("path")) { var left = Utility.NormalizeDepotPath(obj["depotFile"].ToString()); var right = Utility.NormalizeClientPath(obj["path"].ToString()); mappings.Add(left, right); } } } } } catch (Exception ex) { // caught exception, probably because client mappings do not exist. } } return mappings; } public P4CommandResult WhereResults(params string[] paths) { if (paths == null) return null; var args = new List(); foreach (var p in paths) { args.Add(p); } var cmd = new P4Command(GetRepository(), "where", true, args.ToArray()); return cmd.Run(); } public Dictionary GetDepotMappings(IList paths, bool pathsAreDirectories = false) { var mappings = new Dictionary(StringComparer.CurrentCultureIgnoreCase); if (paths.Count > 0) { var fromFiles = new List(); foreach (var path in paths) { var p = path; if (pathsAreDirectories) { p = string.Format("{0}/%", p); } fromFiles.Add(new FileSpec(new ClientPath(p), null)); } IList results = null; try { results = GetConnection().Client.GetClientFileMappings(fromFiles); } catch (Exception ex) { // caught exception, probably because client mappings do not exist } if (results != null) { foreach (var r in results) { mappings.Add(Utility.NormalizeClientPath(r.LocalPath.Path), r.DepotPath.Path); } } } return mappings; } public string PredictDepotPath(string clientPath) { var depotPath = string.Empty; var root = GetConnection().Client.Root; if (root != null) { depotPath = clientPath.Replace(root, "/").Replace("\\", "/"); } return depotPath; } public string PredictClientPath(string depotPath) { var clientPath = string.Empty; var root = GetConnection().Client.Root; if (root != null) { clientPath = Path.Combine(root, depotPath.TrimStart('/').Replace('/', '\\')); } return clientPath; } public FileSpec GetFileSpec(string path) { if(path.StartsWith("//")) { return new FileSpec(new DepotPath(path)); } else { return new FileSpec(new ClientPath(path)); } } public FileSpec[] GetFileSpecs(string[] paths) { var specs = new FileSpec[paths.Count()]; for (var i = 0; i < paths.Count(); i++) { specs[i] = GetFileSpec(paths[i]); } return specs; } /// /// Synchronizes the workspace with the depot /// /// Performs server-side (db.have) sync /// Performs a forced synchronization /// Preview but do not sync /// public IList SyncWorkspaceFiles(VersionSpec versionSpec = null, bool force = false, bool serverOnly = false, bool preview = false, bool notifyOnError = false) { return SyncWorkspacePath("//...", versionSpec: versionSpec, force: force, serverOnly: serverOnly, preview: preview, notifyOnError: notifyOnError); } public IList SyncWorkspacePath(string path, VersionSpec versionSpec = null, bool force = false, bool serverOnly = false, bool preview = false, bool notifyOnError = false) { SyncFilesCmdOptions options = null; if (preview && force && serverOnly) { options = new SyncFilesCmdOptions((SyncFilesCmdFlags.Preview | SyncFilesCmdFlags.Force | SyncFilesCmdFlags.ServerOnly), -1); } else if (preview && serverOnly && !force) { options = new SyncFilesCmdOptions(SyncFilesCmdFlags.Preview | SyncFilesCmdFlags.ServerOnly, -1); } else if (force && serverOnly && !preview) { options = new SyncFilesCmdOptions(SyncFilesCmdFlags.Force | SyncFilesCmdFlags.ServerOnly, -1); } else if (force && preview && !serverOnly) { options = new SyncFilesCmdOptions(SyncFilesCmdFlags.Force | SyncFilesCmdFlags.Preview, -1); } else if (force && !serverOnly && !preview) { options = new SyncFilesCmdOptions(SyncFilesCmdFlags.Force, -1); } else if (preview && !serverOnly && !force) { options = new SyncFilesCmdOptions(SyncFilesCmdFlags.Preview, -1); } else if (serverOnly && !force && !preview) { options = new SyncFilesCmdOptions(SyncFilesCmdFlags.ServerOnly, -1); } else { options = new SyncFilesCmdOptions(SyncFilesCmdFlags.None, -1); } FileSpec pathSpec = null; if (path != null) { pathSpec = new FileSpec(new DepotPath(path), versionSpec); } IList results = null; try { results = GetConnection().Client.SyncFiles(options, pathSpec); } catch (P4Exception ex) { // sync errors could be displayed to user, but for now they are just silently ignored // unless 'notifyOnError' is set if (notifyOnError) { UIHelper.ShowMessage(string.Format("Error synchronizing from server:\n\n{0}", ex.Message)); } } return results; } /// /// Performs a p4 add on the specified file /// /// The client path for the file /// public IList MarkFileForAdd(params string[] paths) { var change = GetOrCreatePendingChangelist(); var specs = GetFileSpecs(paths); var options = new AddFilesCmdOptions(AddFilesCmdFlags.KeepWildcards, change.Id, null); return GetConnection().Client.AddFiles(options, specs); } /// /// Performs a p4 edit on the specified file /// /// The path(s) (either depot or client path) of the file to be edited public IList CheckoutFiles(bool serverOnly=false, params string[] paths) { var change = GetOrCreatePendingChangelist(); var specs = GetFileSpecs(paths); var flags = EditFilesCmdFlags.None; if (serverOnly) { flags = EditFilesCmdFlags.ServerOnly; } var options = new EditCmdOptions(flags, change.Id, null); var client = GetConnection().Client; return client.EditFiles(options, specs); } /// /// /// /// /// /// public IList RenameFile(string sourcePath, string destPath, bool serverOnly=false) { IList moved = null; var md = GetFileMetaData(sourcePath); // some error handling here // if there is no metadata, then the file hasn't even been added to the server yet, so just exit the method if (md == null) return null; // if the file is locked by someone else, then prevent the rename -- we can't do much if the rename occurs in the filesystem // but we can at least prevent the rename in Perforce if (!MetadataHelper.CanEdit(md)) return null; // if the file is deleted in Perforce, then prevent the rename if (md.Action == FileAction.Delete) return null; // now check the destination name var destMd = GetFileMetaData(destPath); if (destMd != null) { // if someone else has the destination locked, bail out if (!MetadataHelper.CanEdit(destMd)) return null; // if the destination has been deleted previously, then continue otherwise bail out if (destMd.HeadAction != FileAction.Delete) return null; } // mark the file for edit if (md.Action != FileAction.Add) { CheckoutFiles(true, sourcePath); } // now do the rename var change = GetOrCreatePendingChangelist(); var sourceFileSpec = GetFileSpec(sourcePath); var destFileSpec = GetFileSpec(destPath); var flags = MoveFileCmdFlags.None; if (serverOnly) { flags = MoveFileCmdFlags.ServerOnly; } var options = new MoveCmdOptions(flags, change.Id, null); moved = GetConnection().Client.MoveFiles(sourceFileSpec, destFileSpec, options); return moved; } public IList RenameFolder(string sourceDir, string destDir, bool serverOnly = false) { IList moved = null; // check to see if the source directory exists if (!IsDirectory(sourceDir)) { // directory does not exist, but maybe it is a path with adds... var md = GetFileMetaData(sourceDir + @"/...", maxItems: 1); // if we have no metadata, then there's nothing to rename if (md == null) return null; // if we aren't seeing ADD actions, then we should bail out too if (md.Action != FileAction.Add) return null; } // check to see if the destination directory exists and bail out if it does // if we are doing a 'serverOnly' rename, then we want the destDir to exist var destExists = IsDirectory(destDir); if (serverOnly && !destExists) return null; if (!serverOnly && destExists) return null; // if we got to this point, things look good... now look for any locks var paths = new List(); paths.Add(sourceDir + @"/..."); var allMD = GetFileMetaData(paths); var lockFound = false; foreach (var md in allMD) { if (!MetadataHelper.CanEdit(md)) { lockFound = true; break; } } // bail out if a lock is found anywhere in the renamed directory if (lockFound) return null; // if we made it here, let's go ahead and do the rename var change = GetOrCreatePendingChangelist(); var sourceDirWC = string.Empty; if (sourceDir.StartsWith("//")) { sourceDirWC = sourceDir + @"/..."; } else { sourceDirWC = sourceDir + @"\..."; } var destDirWC = string.Empty; if (destDir.StartsWith("//")) { destDirWC = destDir + @"/..."; } else { destDirWC = destDir + @"\..."; } var sourceFileSpec = GetFileSpec(sourceDirWC); var destFileSpec = GetFileSpec(destDirWC); CheckoutFiles(serverOnly: true, paths: sourceDirWC); var flags = MoveFileCmdFlags.None; if (serverOnly) { flags = MoveFileCmdFlags.ServerOnly; } var options = new MoveCmdOptions(flags, change.Id, null); moved = GetConnection().Client.MoveFiles(sourceFileSpec, destFileSpec, options); return moved; } public IList CopyFile(string sourcePath, string destPath, bool serverOnly = false) { IList copied = null; var md = GetFileMetaData(sourcePath); // some error handling here // if there is no metadata, then the file hasn't even been added to the server yet, so just exit the method if (md == null) return null; // if the file is locked by someone else, then prevent the rename -- we can't do much if the rename occurs in the filesystem // but we can at least prevent the rename in Perforce if (!MetadataHelper.CanEdit(md)) return null; // if the file is deleted in Perforce, then prevent the rename if (md.Action == FileAction.Delete) return null; // now check the destination name var destMd = GetFileMetaData(destPath); if (destMd != null) { // if someone else has the destination locked, bail out if (!MetadataHelper.CanEdit(destMd)) return null; // if the destination has been deleted previously, then continue otherwise bail out if (destMd.HeadAction != FileAction.Delete) return null; } var change = GetOrCreatePendingChangelist(); var sourceFileSpec = GetFileSpec(sourcePath); var destFileSpecList = new List(); destFileSpecList.Add(GetFileSpec(destPath)); var flags = CopyFilesCmdFlags.None; if(serverOnly) { flags = CopyFilesCmdFlags.Virtual; } var options = new CopyFilesCmdOptions(flags, null, null, null, change.Id, 0); copied = GetConnection().Client.CopyFiles(sourceFileSpec, destFileSpecList, options); return copied; } /// /// /// /// /// public IList DeleteFiles(bool serverOnly = false, params string[] paths) { var change = GetOrCreatePendingChangelist(); var specs = GetFileSpecs(paths); var flags = DeleteFilesCmdFlags.None; if (serverOnly) { flags = DeleteFilesCmdFlags.ServerOnly; } var options = new DeleteFilesCmdOptions(flags, change.Id); var results = GetConnection().Client.DeleteFiles(options, specs); return results; } public IList RevertFiles(bool serverOnly = false, params string[] paths) { var change = GetOrCreatePendingChangelist(); var specs = GetFileSpecs(paths); var flags = RevertFilesCmdFlags.None; if (serverOnly) { flags = RevertFilesCmdFlags.ServerOnly; } var options = new RevertCmdOptions(flags, change.Id); return GetConnection().Client.RevertFiles(options, specs); } public P4CommandResult ReconcileFiles(params string[] paths) { if (paths == null) return null; var change = GetOrCreatePendingChangelist(); var args = new List(); args.Add("-ead"); args.Add("-c"); args.Add(change.Id.ToString()); foreach (var p in paths) { args.Add(p); } var cmd = new P4Command(GetRepository(), "reconcile", true, args.ToArray()); return cmd.Run(); } public IList ShelveFiles(params string[] paths) { var change = GetOrCreatePendingChangelist(); var specs = GetFileSpecs(paths); var options = new ShelveFilesCmdOptions(ShelveFilesCmdFlags.Force, null, change.Id); var results = GetConnection().Client.ShelveFiles(options, specs); return results; } public IList DeleteShelvedFiles(int changeId = 0, params string[] paths) { if (changeId == 0) { var change = GetOrCreatePendingChangelist(); changeId = change.Id; } var specs = GetFileSpecs(paths); var options = new ShelveFilesCmdOptions(ShelveFilesCmdFlags.Delete, null, changeId); return GetConnection().Client.ShelveFiles(options, specs); } public List GetShelvedLocations(string depotPath) { var changeIds = new List(); var changes = GetAllPendingChangelists(); foreach (var c in changes) { if (c.Shelved) { var change = GetChangelist(c.Id, includeShelvedFiles: true); if (change.ShelvedFiles != null) { foreach (var sf in change.ShelvedFiles) { if (sf.Path.Path.Equals(depotPath, StringComparison.CurrentCultureIgnoreCase)) { changeIds.Add(c.Id); break; } } } } } return changeIds; } // this method should look at all pending changelists for this workspace to see if the file is shelved anywhere public bool IsFileShelved(string depotPath) { var shelved = false; var shelveIds = GetShelvedLocations(depotPath); if (shelveIds.Count > 0) { shelved = true; } return shelved; } public IList UnshelveFiles(int changeId = 0, params string[] paths) { var change = GetCurrentPendingChangelist(); if (changeId == 0) { changeId = change.Id; } var specs = GetFileSpecs(paths); var options = new UnshelveFilesCmdOptions(UnshelveFilesCmdFlags.Force, changeId, change.Id); var results = GetConnection().Client.UnshelveFiles(options, specs); return results; } public bool IsFileOpened(string depotPath) { var opened = false; if (!string.IsNullOrEmpty(depotPath)) { var spec = GetFileSpec(depotPath); var specs = new List(); specs.Add(spec); var options = new GetOpenedFilesOptions(GetOpenedFilesCmdFlags.None, null, CurrentClient.Name, Username, 0); var list = GetRepository().GetOpenedFiles(specs, options); if(list != null && list.Count > 0) { foreach (var f in list) { if(f.DepotPath.Path.Equals(depotPath, StringComparison.CurrentCultureIgnoreCase)) { opened = true; break; } } } } return opened; } public bool PathHasAnyOpened(string depotPath) { var anyOpened = false; if (!string.IsNullOrEmpty(depotPath)) { var results = GetAllOpened(depotPath); if (results != null && results.Count > 0) { foreach (var file in results) { if (file.Action == FileAction.Add) continue; anyOpened = true; } } } return anyOpened; } public IList GetAllOpened(string depotPath, int max=0) { var fileSpecs = new List(); fileSpecs.Add(GetFileSpec(depotPath)); var options = new GetOpenedFilesOptions(GetOpenedFilesCmdFlags.AllClients, null, null, null, max); return GetRepository().GetOpenedFiles(fileSpecs, options); } public FileMetaData GetFileMetaData(PathSpec path, int revision, int maxItems = 0) { FileMetaData md = null; var filespec = new FileSpec(path, new Revision(revision)); var flags = GetFileMetadataCmdFlags.Attributes | GetFileMetadataCmdFlags.FileSize | GetFileMetadataCmdFlags.HexAttributes; var options = new GetFileMetaDataCmdOptions(flags, null, null, maxItems, null, null, null); var metaDataList = GetRepository().GetFileMetaData(options, filespec); if (metaDataList != null && metaDataList.Count > 0) { md = metaDataList[0]; } return md; } public FileMetaData GetFileMetaData(string path, string attrFilter = null, bool allVersions = false, int maxItems = 0) { FileMetaData md = null; var paths = new List(); paths.Add(path); var results = GetFileMetaData(paths, attrFilter, allVersions, maxItems); if (results != null && results.Count == 1) { md = results[0]; } return md; } /// /// /// /// /// /// /// public IList GetFileMetaData(List paths, string attrFilter = null, bool allVersions = false, int maxItems = 0) { IList results = new List(); if (paths == null || paths.Count == 0 || paths[0] == null) { return results; } IList specs = new List(); foreach (var path in paths) { if (path.Equals(Constants.DUMMY_DEPOT_PATH)) { continue; } // figure out if this is a depot path or client path // if it starts with //, assume a depot path; client path otherwise if(path.StartsWith("//")) { specs.Add(new FileSpec(new DepotPath(path), null)); } else { specs.Add(new FileSpec(new ClientPath(path), null)); } } if (specs.Count == 0) return null; GetFileMetadataCmdFlags flags = GetFileMetadataCmdFlags.None; if (allVersions) { flags = GetFileMetadataCmdFlags.Attributes | GetFileMetadataCmdFlags.FileSize | GetFileMetadataCmdFlags.HexAttributes | GetFileMetadataCmdFlags.AllRevisions; } else { flags = GetFileMetadataCmdFlags.Attributes | GetFileMetadataCmdFlags.FileSize | GetFileMetadataCmdFlags.HexAttributes; } var options = new GetFileMetaDataCmdOptions(flags, null, null, maxItems, null, null, attrFilter); try { results = GetRepository().GetFileMetaData(specs, options); } catch (Exception ex) { // unable to get metadata -- can happen when bad specs are passed. need to handle gracefully (rather than crashing) } return results; } public IList GetSearchFileMetaData(List paths, bool mappedOnly = false) { GetFileMetadataCmdFlags flags = GetFileMetadataCmdFlags.Attributes | GetFileMetadataCmdFlags.HexAttributes; IList specs = new List(); foreach(var p in paths) { var fs = new FileSpec(new DepotPath(p), null); specs.Add(fs); } var filter = new StringBuilder(); filter.Append("headRev & ^headAction=delete & ^headAction=move/delete"); if (mappedOnly) { filter.Append(" & isMapped"); } var options = new GetFileMetaDataCmdOptions(flags, filter.ToString(), null, -1, null, null, "tags"); return GetRepository().GetFileMetaData(specs, options); } public SizeData GetPathSizes(string depotPath) { SizeData data = null; if (IsDirectory(depotPath) && !depotPath.EndsWith("/...")) { depotPath = depotPath.TrimEnd('/') + "/..."; } var args = new List(); args.Add("-s"); args.Add(depotPath); var cmd = new P4Command(GetRepository(), "sizes", true, args.ToArray()); var results = cmd.Run(); if (results.TaggedOutput != null) { data = new SizeData(); foreach (TaggedObject obj in results.TaggedOutput) { if (obj.ContainsKey("path")) { data.Path = obj["path"].ToString(); } if (obj.ContainsKey("fileCount")) { data.FileCount = int.Parse(obj["fileCount"].ToString()); } if (obj.ContainsKey("fileSize")) { data.FileSize = long.Parse(obj["fileSize"].ToString()); } } } return data; } public string GetFileFromServer(string depotPath, int revision = -1) { string filePath = null; if (depotPath != null) { var extIndex = depotPath.LastIndexOf('.'); if (extIndex < depotPath.Length) { var extension = depotPath.Substring(extIndex); var path = Path.GetTempPath(); var fileName = Guid.NewGuid().ToString() + extension; filePath = Path.Combine(path, fileName); var options = new GetFileContentsCmdOptions(GetFileContentsCmdFlags.None, filePath); FileSpec spec = null; if (revision > 0) { spec = FileSpec.DepotSpec(depotPath, revision); } else if(revision == 0) { spec = new FileSpec(new DepotPath(depotPath), VersionSpec.None); } else { spec = new FileSpec(new DepotPath(depotPath), VersionSpec.Head); } var results = GetRepository().GetFileContents(options, spec); } } return filePath; } public string GetDirectoryFromServer(string depotPath, string targetDir = null) { Log.Debug(string.Format("GetDirectoryFromServer {0} -> {1}", depotPath, targetDir)); if (depotPath != null) { depotPath = depotPath.TrimEnd('.').TrimEnd('/'); var dirname = depotPath.Substring(depotPath.LastIndexOf('/') + 1); var parentDepotPath = depotPath.Substring(0, depotPath.LastIndexOf('/') + 1); if(string.IsNullOrEmpty(targetDir)) { targetDir = Path.GetTempPath(); } var dirSpec = GetFileSpec(depotPath + "/..."); var filesOptions = new FilesCmdOptions(FilesCmdFlags.None, 0); var files = GetRepository().GetFiles(filesOptions, dirSpec); foreach (var file in files) { var fileDepotPath = file.DepotPath.Path; var subPath = fileDepotPath.Replace(parentDepotPath, "").Replace('/', '\\'); var outputFilePath = Path.Combine(targetDir, subPath); var getFileContentsOptions = new GetFileContentsCmdOptions(GetFileContentsCmdFlags.None, outputFilePath); var fileSpec = GetFileSpec(fileDepotPath); var results = GetRepository().GetFileContents(getFileContentsOptions, fileSpec); } } return targetDir; } public string GetFileFromShelf(string depotPath, int changeId = -1) { string filePath = null; if (depotPath != null) { if (changeId < 1) { changeId = GetCurrentPendingChangelist().Id; } var extIndex = depotPath.LastIndexOf('.'); if (extIndex < depotPath.Length) { var extension = depotPath.Substring(extIndex); var path = Path.GetTempPath(); var fileName = Guid.NewGuid().ToString() + extension; filePath = Path.Combine(path, fileName); var shelf = new ShelvedInChangelistIdVersion(changeId); var options = new GetFileContentsCmdOptions(GetFileContentsCmdFlags.None, filePath); var spec = new FileSpec(new DepotPath(depotPath), shelf); var results = GetRepository().GetFileContents(options, spec); } } return filePath; } public IList GetFileHistory(string path) { var flags = GetFileHistoryCmdFlags.IncludeInherited|GetFileHistoryCmdFlags.FullDescription; var fileSpec = GetFileSpec(path); var options = new GetFileHistoryCmdOptions(flags, 0, 0); return GetRepository().GetFileHistory(options, fileSpec); } public SubmitResults RollbackFileToRevision(string path, int revision) { var fileSpec = FileSpec.DepotSpec(path, revision); var desc = string.Format("Rolling file {0} back to revision {1}", path, revision); var change = CreateChangelist(desc); var mdOptions = new GetFileMetaDataCmdOptions(GetFileMetadataCmdFlags.None, null, null, -1, null, null, null); var mdList = GetRepository().GetFileMetaData(mdOptions, FileSpec.DepotSpec(path)); if (mdList.Count != 1) return null; var md = mdList[0]; if (md.HeadRev <= revision) return null; // sync the desired revision of the file var syncOptions = new SyncFilesCmdOptions(SyncFilesCmdFlags.Force, 0); GetConnection().Client.SyncFiles(syncOptions, fileSpec); // edit the file var editOptions = new EditCmdOptions(EditFilesCmdFlags.None, change.Id, null); GetConnection().Client.EditFiles(editOptions, FileSpec.DepotSpec(path)); // sync the head revision of the file GetConnection().Client.SyncFiles(syncOptions, FileSpec.DepotSpec(path)); // resolve the file (ay) var resolveOptions = new ResolveCmdOptions(ResolveFilesCmdFlags.AutomaticYoursMode, change.Id); GetConnection().Client.ResolveFiles(resolveOptions, fileSpec); // submit the changes var clientSubmitOptions = new ClientSubmitOptions(); clientSubmitOptions.SubmitType = SubmitType.RevertUnchanged; var submitOptions = new SubmitCmdOptions(SubmitFilesCmdFlags.None, change.Id, null, null, clientSubmitOptions); try { return GetConnection().Client.SubmitFiles(submitOptions, null); } catch (Exception ex) { var deleteChangelistOptions = new ChangeCmdOptions(ChangeCmdFlags.None); GetRepository().DeleteChangelist(change, deleteChangelistOptions); return null; } } public SubmitResults RollbackFolderToChangelist(string path, int changeId) { // add wildcard to the path if it isn't there if (!path.EndsWith("/...")) { path = path + "/..."; } // create a changelist var desc = string.Format("Rolling folder {0} back to change @{1}", path, changeId); var change = CreateChangelist(desc); // create a filespec with the changeID var fileSpec = new FileSpec(new DepotPath(path), new ChangelistIdVersion(changeId)); var deletedList = new List(); var addedList = new List(); var updatedList = new List(); // preview the sync string[] previewArgs = { "-f", "-n", fileSpec.ToString() }; var p4cmd = GetConnection().CreateCommand("sync", true, previewArgs); var p4cmdResults = p4cmd.Run(); if (p4cmdResults.TaggedOutput != null) { foreach (TaggedObject obj in p4cmdResults.TaggedOutput) { string depotFile = null; var res = obj.TryGetValue("depotFile", out depotFile); if (depotFile != null) { var depotFileSpec = GetFileSpec(depotFile); if (obj.ContainsKey("action")) { var action = obj["action"].ToString(); if (action.Equals("deleted")) { deletedList.Add(depotFileSpec); } else if (action.Equals("added")) { addedList.Add(depotFileSpec); } else if (action.Equals("updated")) { updatedList.Add(depotFileSpec); } } } } } // sync to the desired changelist var syncOptions1 = new SyncFilesCmdOptions(SyncFilesCmdFlags.Force, 0); var syncResults1 = GetConnection().Client.SyncFiles(syncOptions1, fileSpec); if (updatedList.Count > 0) { // edit the files in the path var editOptions = new EditCmdOptions(EditFilesCmdFlags.None, change.Id, null); var editResults = GetConnection().Client.EditFiles(editOptions, updatedList.ToArray()); // sync the head revision of the file var syncResults2 = GetConnection().Client.SyncFiles(syncOptions1, updatedList.ToArray()); // resolve the file (ay) var resolveOptions = new ResolveCmdOptions(ResolveFilesCmdFlags.AutomaticYoursMode, change.Id); var resolveResults = GetConnection().Client.ResolveFiles(resolveOptions, updatedList.ToArray()); } if (addedList.Count > 0) { // find any deleted items that need to be re-added with the -d (downgrade) flag var addOptions = new AddFilesCmdOptions(AddFilesCmdFlags.Downgrade | AddFilesCmdFlags.KeepWildcards, change.Id, null); var results = GetConnection().Client.AddFiles(addOptions, addedList.ToArray()); } if (deletedList.Count > 0) { // delete any items that aren't supposed to be there var deleteOptions = new DeleteFilesCmdOptions(DeleteFilesCmdFlags.DeleteUnsynced, change.Id); var results = GetConnection().Client.DeleteFiles(deleteOptions, deletedList.ToArray()); } // submit the changes var clientSubmitOptions = new ClientSubmitOptions(); clientSubmitOptions.SubmitType = SubmitType.SubmitUnchanged; var submitOptions = new SubmitCmdOptions(SubmitFilesCmdFlags.None, change.Id, null, null, clientSubmitOptions); try { var submitResult = GetConnection().Client.SubmitFiles(submitOptions, null); return submitResult; } catch (Exception ex) { var deleteChangelistOptions = new ChangeCmdOptions(ChangeCmdFlags.None); GetRepository().DeleteChangelist(change, deleteChangelistOptions); throw new ApplicationException(ex.Message); } } #endregion #region CONSTRUCTORS public PerforceHelper(string serverUri, string username) { _serverUri = serverUri; _username = username; _server = null; var cache = MemoryCache.Default; cache.Remove(REPO_CACHE_KEY); var repo = GetRepository(useCache: false, testConnection: false); var addr = repo.Server.Address; if(repo.Connection == null) { throw new ApplicationException("ERROR: unable to connect to server"); } } #endregion #region ACCESSOR FUNCTIONS public string Username { get { return _username; } set { _username = value; } } public string ServerURI { get { return _serverUri; } set { _serverUri = value; } } public string Password { set { _password = value; } } public string Ticket { get { return _ticket; } set { _ticket = value; } } public bool ClientEnabled { get { var enabled = false; if (CurrentClient != null) { enabled = true; } return enabled; } } #endregion #region PRIVATE COMMUNICATION METHODS private Server _server; private Server GetServer() { try { if (_server == null) { _server = new Server(new ServerAddress(_serverUri)); } return _server; } catch (Exception e) { throw new P4HelperException(e.Message); } } private static string REPO_CACHE_KEY = "repository"; private Repository GetRepository(bool useCache = true, bool testConnection = false) { ObjectCache cache = null; Repository repo = null; if (testConnection) { // test the connection with a 5-second timeout. Why 5 seconds? no real // reason. it had to be some timeout value, and 5 seconds seemed a reasonably // short time to wait without having false positives. if (!Utility.CheckTCPConnection(_serverUri, timeout: 5)) { var exception = new P4HelperException("Cannot reach the server"); UIHelper.CriticalError(exception); } } if (useCache) { cache = MemoryCache.Default; repo = (Repository)cache.Get(REPO_CACHE_KEY); } if (repo == null) { repo = new Repository(GetServer()); repo.Connection.UserName = _username; if (_client != null) { repo.Connection.SetClient(_client.Name); } var options = new Options(); repo.Connection.Connect(options); repo.Connection.CommandTimeout = TimeSpan.FromMinutes(20); if (useCache) { var policy = new CacheItemPolicy(); policy.SlidingExpiration = TimeSpan.FromSeconds(30.0); cache.Add(REPO_CACHE_KEY, repo, policy); } } return repo; } private Connection GetConnection(bool useCache = true, bool testConnection = false) { Connection con = null; var repo = GetRepository(useCache, testConnection); if (repo != null) { con = repo.Connection; } return con; } #endregion [Conditional("DEBUG")] public static void Debug(String logMessage) { _log.Debug(logMessage); } #region CLEANUP protected virtual void Dispose(bool disposing) { if (disposing) { // dispose managed resources GetRepository().Dispose(); } // free native resources } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion } #region EXCEPTION CLASS [Serializable] public class P4HelperException : Exception, ISerializable { public P4HelperException(string msg) : base(msg) { } } #endregion }