// // 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; namespace Perforce.Helper { public class DepotMapping { Dictionary depotMapping; Dictionary clientMapping; public DepotMapping() { depotMapping = new Dictionary(StringComparer.CurrentCultureIgnoreCase); clientMapping = new Dictionary(StringComparer.CurrentCultureIgnoreCase); } public int Count { get { return depotMapping.Count; } } public void Add(string depotFile, string clientFile) { if (depotFile.EndsWith("/%25")) depotFile = depotFile.Substring(0, depotFile.Length - 4); depotFile = PerforceHelper.EscapePath(depotFile.TrimEnd('/', '%')); clientFile = PerforceHelper.EscapePath(clientFile.TrimEnd('\\', '%')); depotMapping.Add(depotFile, clientFile); clientMapping.Add(clientFile, depotFile); } private bool IsMapped(string path) { if (Utility.IsDepotPath(path)) { return depotMapping.Keys.Contains(path); } else { return clientMapping.Keys.Contains(path); } } public bool IsDirMapped(string path) { if (Utility.IsDepotPath(path)) { path = path.TrimEnd('/'); } else { path = path.TrimEnd('\\'); } return IsMapped(PerforceHelper.EscapePath(path)); } public string GetMappedDir(string path) { string result = null; path = PerforceHelper.EscapePath(path); if (Utility.IsDepotPath(path)) { path = path.TrimEnd('/'); result = depotMapping[path]; } else { path = path.TrimEnd('\\'); result = clientMapping[path]; } return result; } public bool IsFileMapped(string path) { return IsMapped(path); } } public class SyncMetaData { public DepotPath DepotPath { get; set; } public LocalPath LocalPath { get; set; } public ClientPath ClientPath { get; set; } public string Action { get; set; } //depotFile='//depot/jam/main/fred.txt' //clientFile='c:\temp\TEST_P4API.NET_CLIENT\depot\Jam\MAIN\fred.txt' //rev='2' //action='added' //fileSize='30' //totalFileSize='2350026' //totalFileCount='104' //change='12398' func='client-FstatInfo' public void FromCmdTaggedData(TaggedObject obj) { if (obj.ContainsKey("clientFile")) { string path = obj["clientFile"]; if (path.StartsWith("//")) { ClientPath = new ClientPath(obj["clientFile"]); } else { ClientPath = new ClientPath(obj["clientFile"]); LocalPath = new LocalPath(obj["clientFile"]); } } if (obj.ContainsKey("depotFile")) { string p = obj["depotFile"]; DepotPath = new DepotPath(p); } if (obj.ContainsKey("action")) { Action = obj["action"]; } } } 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 P4Server _pserver = null; private static readonly ILog _log = LogManager.GetLogger(typeof(PerforceHelper)); // For testing purposes public WorkspaceWatcher TestWorkspaceWatcher = null; #region LOGGING public static void LoggingFunction(int log_level, String source, String message) { if (!_log.IsDebugEnabled) return; _log.DebugFormat("{0}:{1}:{2}", log_level, source, message); } private static void LogP4Exception(P4Exception p4e) { _log.WarnFormat("Exception: {0}", p4e.Message); } #endregion #region LOGIN AND CONNECTION TESTING /// /// Creates a connection to the Perforce server /// /// true if connected, false otherwise public bool Connect() { Log.TraceFunction(); var connected = false; var con = GetConnection(); if (con.Status == ConnectionStatus.Disconnected) { try { con.Connect(null); } catch (P4Exception p4e) { LogP4Exception(p4e); throw new P4HelperException(p4e.Message); } } if (con.Status == ConnectionStatus.Connected) { connected = true; } return connected; } /// /// Disconnects from the Perforce server /// public void Disconnect() { Log.TraceFunction(); 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) { Log.TraceFunction(); 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 p4e) { LogP4Exception(p4e); throw new P4HelperException(p4e.Message); } } return result; } /// /// Checks to see if the current session is logged in /// /// true if logged in, false otherwise public bool IsLoggedIn() { Log.TraceFunction(); var result = false; var con = GetConnection(useCache: true, 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 p4e) { // do nothing... an exception is thrown if login -s fails, indicating that // the user is not logged in LogP4Exception(p4e); } } return result; } /// /// Logs out the current session /// /// returns the result of the logout operation public bool Logout() { Log.TraceFunction(); 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 /// /// Converts into depot syntax where appropriate - handling special chars /// /// converted path public static string EscapePath(string path) { if (!path.Contains("%") && !path.Contains("@") && !path.Contains("*") && !path.Contains("#")) { return path; } if (!path.Contains("%25") && !path.Contains("%23") && !path.Contains("%40") && !path.Contains("%2A")) { return PathSpec.EscapePath(path); } return path; } public static List EscapePaths(List paths) { var results = new List(); foreach (var p in paths) { if (Utility.IsDepotPath(p)) results.Add(EscapePath(p)); else results.Add(p); } return results; } public static List EscapePaths(params string[] paths) { return EscapePaths(new List(paths)); } /// /// Converts into depot syntax where appropriate - handling special chars /// /// true if connected, false otherwise public static string UnescapePath(string path) { if (path.Contains("%25") || path.Contains("%23") || path.Contains("%40") || path.Contains("%2A")) return PathSpec.UnescapePath(path); return path; } private DepotPath MkEscapedDepotPath(string path) { return new DepotPath(EscapePath(path)); } private DepotPath MkUnescapedDepotPath(string path) { return new DepotPath(UnescapePath(path)); } private ClientPath MkClientPath(string path) { return new ClientPath(path); } public long GetServerTime() { return Utility.GetEpochTime(GetServer().Metadata.Date.ToUniversalTime()); } public bool IsServerCaseSensitive() { return GetServer().Metadata.CaseSensitive; } public string GetKey(string name) { Log.TraceFunction(); 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) { Log.TraceFunction(); if (string.IsNullOrEmpty(userid)) return null; var repo = GetRepository(); return repo.GetUser(userid); } public bool SetPassword(string oldPassword, string newPassword) { Log.TraceFunction(); var repo = GetRepository(); return repo.Connection.SetPassword(oldPassword, newPassword); } #endregion #region LISTING OPERATIONS private string AppendWildcard(string path, string wildcard) { if (path.EndsWith("#0")) path = path.TrimEnd('#', '0'); if (path.EndsWith(wildcard)) return path; if (Utility.IsDepotPath(path)) return path + "/" + wildcard; else return path + "\\" + wildcard; } public bool IsDirectory(string path) { Log.TraceFunction(); if (path.Equals("//")) return true; if (path.EndsWith("/")) return true; if (path.EndsWith("/...")) return true; var repo = GetRepository(); string[] p = { EscapePath(path) }; var options = new GetDepotDirsCmdOptions(GetDepotDirsCmdFlags.None, null); try { var results = repo.GetDepotDirs(options, p); if (results.Count == 1) return true; } catch (P4Exception p4e) { LogP4Exception(p4e); } var clientPath = PredictClientPath(path); var exists = Directory.Exists(clientPath); return exists; } public bool IsFile(string path) { Log.TraceFunction(); var repo = GetRepository(); string[] p = { EscapePath(path) }; var specs = GetEscapedFileSpecs(p); var options = new GetDepotFilesCmdOptions(GetDepotFilesCmdFlags.None, 0); try { var results = repo.GetDepotFiles(specs, options); return results.Count == 1; } catch (P4Exception p4e) { LogP4Exception(p4e); return false; } } /// /// 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) { Log.TraceFunction(); IList depots = null; try { var repo = GetRepository(); if (repo != null) { var allDepots = repo.GetDepots(); if (localOnly) { depots = allDepots.Where(d => d.Type == DepotType.Local).ToList(); } else { depots = allDepots; } } } catch (P4Exception p4e) { LogP4Exception(p4e); } 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 - note these are unescaped (not depot syntax for special chars) public IList ListDirectories(string path) { Log.TraceFunction(); var repo = GetRepository(); if (!path.EndsWith("/")) { path += "/"; } string[] p = { EscapePath(path) + "*" }; var options = new GetDepotDirsCmdOptions(GetDepotDirsCmdFlags.None, null); try { var dirs = repo.GetDepotDirs(options, p); IList results = new List(); foreach (var d in dirs) { results.Add(UnescapePath(d.ToString())); } return results; } catch (P4Exception p4e) { LogP4Exception(p4e); return new List(); } } /// /// 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) { Log.TraceFunction(); var repo = GetRepository(); var filespec = new List(); filespec.Add(new FileSpec(new DepotPath(UnescapePath(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"); try { var results = repo.GetFileMetaData(filespec, options); return results; } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } #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) { Log.TraceFunction(); var repo = GetRepository(); var options = new ClientsCmdOptions(ClientsCmdFlags.None, _username, null, -1, null); try { 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; } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } /// /// 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) { Log.TraceFunction(); Client client = null; try { 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); } catch (P4Exception p4e) { LogP4Exception(p4e); } return client; } /// /// Creates a client based on the client specification /// /// The client specification to create /// The client created public Client SaveClient(Client client) { Log.TraceFunction(); try { var result = GetRepository().CreateClient(client); CurrentClient = client; return CurrentClient; } catch (P4Exception p4e) { LogP4Exception(p4e); return client; } } public Client GetClient(string name) { Log.TraceFunction(); try { var result = GetRepository().GetClient(name); return result; } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } public Client UpdateClient(Client client) { Log.TraceFunction(); try { GetRepository().UpdateClient(client); CurrentClient = client; return CurrentClient; } catch (P4Exception p4e) { LogP4Exception(p4e); return client; } } /// /// 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) { Log.TraceFunction(); bool found = false; var repo = GetRepository(); var options = new ClientsCmdOptions(ClientsCmdFlags.IgnoreCase, null, name, -1, null); try { var matching = repo.GetClients(options); if (matching != null && matching.Count > 0) { found = true; } } catch (P4Exception p4e) { LogP4Exception(p4e); } return found; } // Returns true if the specified folder path is the LHS of a specific // client view mapping. Intended for use to decide if a folder can be // Synced or Unsynced. public bool ClientFolderMappingContains(string path) { Log.TraceFunction(); path = AppendWildcard(path, "..."); if (CurrentClient.ViewMap == null) return false; MapEntry line = CreateMapEntry(path, CurrentClient.Name, MapType.Include); return MappingContains(CurrentClient.ViewMap, line, path); } public string MapToDepot(string path, ViewMap vm=null) { if (Utility.IsDepotPath(path)) return path; if (_pserver == null) _pserver = new P4Server(_serverUri, _username, _password, _client.Name); P4MapApi map = new P4MapApi(_pserver); if (vm == null) vm = CurrentClient.ViewMap; foreach (var line in vm) { map.Insert(line.Left.Path, line.Right.Path, P4MapApi.Type.Include); } string relativePath = path.Replace(CurrentClient.Root, ""); relativePath = relativePath.TrimStart('\\').Replace("\\", "/"); string clientPath = string.Format(("//{0}/{1}"), CurrentClient.Name, relativePath); var depotPath = map.Translate(clientPath, P4MapApi.Direction.RightLeft); return depotPath; } private bool MappingContains(ViewMap vm, MapEntry me, string path) { bool caseSensitive = IsServerCaseSensitive(); // Convert from client path to depot path if necessary path = MapToDepot(path, vm); if (path == null) return false; path = EscapePath(path); foreach (var mapping in vm) { var left = mapping.Left.Path; if (left.EndsWith("/...")) { if (caseSensitive) { if (path.StartsWith(left.TrimEnd('.'))) return true; } else { if (path.StartsWith(left.TrimEnd('.'), StringComparison.CurrentCultureIgnoreCase)) return true; } } } return false; } public Client ClientFolderMappingAdd(string depotPath) { Log.TraceFunction(); Client c = GetClient(_client.Name); MapEntry line = CreateMapEntry(depotPath, _client.Name, MapType.Include); if (c.ViewMap == null) { c.ViewMap = new ViewMap(); } if (!MappingContains(c.ViewMap, line, depotPath)) { c.ViewMap.Add(line); } return UpdateClient(c); } public Client ClientFolderMappingRemove(string depotPath) { Log.TraceFunction(); Client updated = null; Client c = GetClient(_client.Name); depotPath = AppendWildcard(depotPath, "..."); Log.Debug(string.Format("Path: {0}", depotPath)); var entry = CreateMapEntry(depotPath, _client.Name, MapType.Include); var changed = false; if (c.ViewMap != null && c.ViewMap.Count > 0) { if (MappingContains(c.ViewMap, entry, depotPath)) { Log.Debug("Removed view line"); 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) { Log.Debug("Adding exlude mapping"); 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) { Log.TraceFunction(); var left = MkEscapedDepotPath(depotPath); var clientPath = string.Format("//{0}/{1}", clientName, EscapePath(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(); try { var client = repo.GetClient(name); if (client != null) { var options = new DeleteFilesCmdOptions(DeleteFilesCmdFlags.None, -1); repo.DeleteClient(client, options); } } catch (P4Exception p4e) { LogP4Exception(p4e); } } /// /// CurrentClient property /// public Client CurrentClient { get { return _client; } set { _client = value; SetClient(_client.Name) ; } } public void SetClient(string name) { Log.TraceFunction(); 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) { Log.TraceFunction(); try { var change = new Changelist(); change.OwnerName = _username; change.ClientId = CurrentClient.Name; change.Description = description; var repo = GetRepository(); change = repo.CreateChangelist(change); EscapeChangelistFilePaths(change); return change; } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } /// /// 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) { Log.TraceFunction(); 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); } try { var result = repo.GetChangelist(id, opts); EscapeChangelistFilePaths(result); return result; } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } public IList GetAllPendingChangelists() { Log.TraceFunction(); IList result = new List(); var repo = GetRepository(); var options = new ChangesCmdOptions(ChangesCmdFlags.None, CurrentClient.Name, 0, ChangeListStatus.Pending, Username); try { result = repo.GetChangelists(options, null); foreach (var change in result) { EscapeChangelistFilePaths(change); } } catch (P4Exception p4e) { LogP4Exception(p4e); } return result; } public Changelist GetCurrentPendingChangelist(bool shelved=false) { Log.TraceFunction(); Changelist current = null; var repo = GetRepository(); var options = new ChangesCmdOptions(ChangesCmdFlags.None, CurrentClient.Name, 1, ChangeListStatus.Pending, Username); try { 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); EscapeChangelistFilePaths(current); } } catch (P4Exception p4e) { LogP4Exception(p4e); } return current; } public void DeletePendingChangeList() { Log.TraceFunction(); try { var change = GetCurrentPendingChangelist(); if (change != null) { var repo = GetRepository(); var options = new Options(); repo.DeleteChangelist(change, options); } } catch (P4Exception p4e) { LogP4Exception(p4e); } } // 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() { Log.TraceFunction(); 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.RunFstat(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.RunFstat(f.DepotPath.Path); if (!System.IO.File.Exists(md.LocalPath.Path)) { filesToRevert.Add(f.ClientPath.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.RunFstat(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() { Log.TraceFunction(); IList reopenedFiles = null; var repo = GetRepository(); var currentChange = GetOrCreatePendingChangelist(); var openedSpecs = new List(); var reopenSpecs = new List(); openedSpecs.Add(new FileSpec(MkEscapedDepotPath("//..."))); // GetOpenedFiles doesn't escape! try { var openedOptions = new GetOpenedFilesOptions(GetOpenedFilesCmdFlags.None, null, CurrentClient.Name, Username, 0); var list = GetRepository().GetOpenedFiles(openedSpecs, openedOptions); if (list != null) { foreach (var i in list) { if (i.ChangeId != currentChange.Id) { reopenSpecs.Add(new FileSpec(i.ClientPath)); } } } } catch (P4Exception) { } if (reopenSpecs.Count > 0) { var options = new ReopenCmdOptions(currentChange.Id, null); try { reopenedFiles = repo.Connection.Client.ReopenFiles(reopenSpecs, options); } catch (P4Exception) { } } return FixFileSpecs(reopenedFiles); } public IList MoveFilesToNewChangelist(IList files, int changeId=0) { Log.TraceFunction(); 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(UnescapePath(f)))); } else { fileSpecs.Add(new FileSpec(new ClientPath(UnescapePath(f)))); } } try { return FixFileSpecs(repo.Connection.Client.ReopenFiles(fileSpecs, options)); } catch (P4Exception) {} return null; } /// /// Deletes the changelist /// /// The changelist Id number to delete public void DeleteChangelist(int id) { Log.TraceFunction(); var change = GetChangelist(id); if (change != null && change.Pending) { DeleteChangelist(change); } } /// /// Deletes the changelist /// /// The changelist object to delete public void DeleteChangelist(Changelist change) { Log.TraceFunction(); var repo = GetRepository(); var options = new ChangeCmdOptions(ChangeCmdFlags.Delete); repo.DeleteChangelist(change, options); } /// /// Lists 50 submitted changelists on the path /// /// The specified path public IList ListChanges(string path) { Log.TraceFunction(); var repo = GetRepository(); if (IsDirectory(path) && !path.EndsWith("/...")) { path = path + "/..."; } // Unescaped local paths can cause problems - convert to depot paths first var depotPath = MapToDepot(path); if (depotPath == null) depotPath = path; var spec = GetEscapedFileSpec(depotPath); // GetChangelists doesn't escape var options = new ChangesCmdOptions(ChangesCmdFlags.FullDescription, null, 50, ChangeListStatus.Submitted, null); try { return repo.GetChangelists(options, spec); } catch (P4Exception p4e) { Log.Exception(p4e); return null; } } public ResultsWrapper SubmitSingleFile(string path, string description) { Log.TraceFunction(); var change = CreateChangelist(description); var reopenOptions = new ReopenCmdOptions(change.Id, null); var spec = GetEscapedFileSpec(path); // Reopen doesn't escape try { GetRepository().Connection.Client.ReopenFiles(reopenOptions, spec); return SubmitChangelist(change.Id, description, revertUnchanged: true); } catch (P4Exception) { } return null; } public ResultsWrapper SubmitChangelist(int id, string description=null, bool revertUnchanged=true) { Log.TraceFunction(); 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) { Log.TraceFunction(); 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 { UnescapeChangelistFilePaths(change); 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) { Log.TraceFunction(); // 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) { Log.TraceFunction(); if (change == null) { change = GetCurrentPendingChangelist(shelved: true); } if (change == null) { return null; } return change.ShelvedFiles; } /// /// /// /// public IList RevertChangelist(Changelist change=null) { Log.TraceFunction(); // 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); try { return FixFileSpecs(GetRepository().Connection.Client.RevertFiles(options, fileSpec)); } catch (P4Exception) { } return null; } public Changelist GetOrCreatePendingChangelist() { Log.TraceFunction(); var change = GetCurrentPendingChangelist(); if (change == null) { change = CreateChangelist(Constants.GENERATED_CHANGELIST_DESCRIPTION); } return change; } #endregion #region FILE OPERATIONS public bool PathExists(string depotPath) { Log.TraceFunction(); // first check to see if the path is an existing file var fileResults = RunCmd("files", depotPath, new List() {"-e"}); if (fileResults != null && fileResults.Count == 1) return true; // if the file does not exist, check to see if it is a directory var dirResults = RunCmd("dirs", depotPath); if (dirResults != null && dirResults.Count == 1) return true; return false; } public bool IsDirectoryMapped(string dir) { Log.TraceFunction(); // normalize directory dir = dir.TrimEnd('/'); var list = new List(); list.Add(dir); var mappings = GetClientMappings(list, true); return mappings.IsDirMapped(dir); } public bool IsPathInClientView(string depotPath) { Log.TraceFunction(); 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; } public DepotMapping GetClientMappings(IList paths, bool pathsAreDirectories=false) { Log.TraceFunction(); var mappings = new DepotMapping(); if (paths != null && paths.Count > 0) { var fileArr = new List(); foreach (var path in paths) { var p = path; if (pathsAreDirectories) { if (Utility.IsDepotPath(p)) { p = string.Format("{0}/%", p); } else { 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 depotFile = Utility.NormalizeDepotPath(obj["depotFile"].ToString()); var clientFile = Utility.NormalizeClientPath(obj["path"].ToString()); mappings.Add(depotFile, clientFile); } } } } } catch (Exception) { // caught exception, probably because client mappings do not exist. } } return mappings; } public P4CommandResult WhereResults(params string[] paths) { Log.TraceFunction(); if (paths == null) return null; var cmd = new P4Command(GetRepository(), "where", true, EscapePaths(paths).ToArray()); return cmd.Run(); } public DepotMapping GetDepotMappings(IList paths, bool pathsAreDirectories = false) { Log.TraceFunction(); var mappings = new DepotMapping(); 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) { // caught exception, probably because client mappings do not exist } if (results != null) { foreach (var r in results) { mappings.Add(r.DepotPath.Path, Utility.NormalizeClientPath(r.LocalPath.Path)); } } } return mappings; } public string PredictDepotPath(string clientPath) { Log.TraceFunction(); var depotPath = string.Empty; var root = GetConnection().Client.Root; if (root != null) { depotPath = clientPath.Replace(root, "/").Replace("\\", "/"); } return depotPath; } public string PredictClientPath(string depotPath) { Log.TraceFunction(); var clientPath = string.Empty; var root = GetConnection().Client.Root; if (root != null) { clientPath = Path.Combine(root, depotPath.TrimStart('/').Replace('/', '\\')); } return clientPath; } public FileSpec GetEscapedFileSpec(string path) { Log.TraceFunction(); if (Utility.IsDepotPath(path)) { return new FileSpec(MkEscapedDepotPath(path)); } else { return new FileSpec(MkClientPath(path)); } } public FileSpec GetUnescapedFileSpec(string path) { Log.TraceFunction(); if (Utility.IsDepotPath(path)) { return new FileSpec(MkUnescapedDepotPath(path)); } else { return new FileSpec(MkClientPath(path)); } } public FileSpec[] GetEscapedFileSpecs(string[] paths) { Log.TraceFunction(); var specs = new FileSpec[paths.Count()]; for (var i = 0; i < paths.Count(); i++) { specs[i] = GetEscapedFileSpec(paths[i]); } return specs; } public FileSpec[] GetUnescapedFileSpecs(string[] paths) { Log.TraceFunction(); var specs = new FileSpec[paths.Count()]; for (var i = 0; i < paths.Count(); i++) { specs[i] = GetUnescapedFileSpec(paths[i]); } return specs; } /// /// Synchronizes the specified pathwith repository /// /// Various sync flags possible, e.g. -f /// Whether to report sync errors via UI /// public IList RunSync(string path, string revSpec = null, List syncArgs = null, bool notifyOnError = false) { List results = new List(); path = EscapePath(path); if (revSpec != null) path += revSpec; var paths = new List() {path}; var ws = GetWorkspaceWatcher(); bool syncErrorOccurred = false; string syncError = ""; P4Command cmd = null; P4CommandResult r = null; var sr = new SyncReporter(ws, path); try { if (ws != null) ws.SyncStart(path); cmd = new P4Command(GetRepository(), "sync", true, paths.ToArray()); cmd.Connection.TaggedOutputReceived += sr.TaggedOutputCallbackFn; if (syncArgs == null) r = cmd.Run(); else r = cmd.Run(syncArgs.ToArray()); } catch (P4Exception ex) { syncErrorOccurred = true; syncError = ex.Message; } finally { cmd.Connection.TaggedOutputReceived -= sr.TaggedOutputCallbackFn; } // This is probably not necessary any more as have reported to WorkspaceWatcher as we went, // but we return them to the caller. if (!((r == null) || (r.TaggedOutput == null) || (r.TaggedOutput.Count <= 0))) { foreach (P4.TaggedObject obj in r.TaggedOutput) { if ((obj.Count <= 2) && (obj.ContainsKey("desc"))) { // hack, but this not really a file, it's just a // the description of the change if -e option is // specified, so skip it continue; } SyncMetaData smd = new SyncMetaData(); smd.FromCmdTaggedData(obj); results.Add(smd); } } if (ws != null) { ws.SyncComplete(path); } if (syncErrorOccurred && notifyOnError) UIHelper.ShowMessage(string.Format("Error synchronizing from server:\n\n{0}", syncError)); return results; } private class SyncReporter { private WorkspaceWatcher _ws = null; private string _originalPath = ""; public SyncReporter(WorkspaceWatcher ws, string originalPath) { _ws = ws; _originalPath = originalPath; } // This will immediately report sync output to WorkspaceWatcher public void TaggedOutputCallbackFn(uint cmdId, int objId, P4.TaggedObject obj) { if (obj == null || _ws == null) { return; } if ((obj.Count <= 2) && (obj.ContainsKey("desc"))) { // hack, but this not really a file, it's just a // the description of the change if -e option is // specified, so skip it return; } var smd = new SyncMetaData(); smd.FromCmdTaggedData(obj); _ws.SyncRecordProgress(_originalPath, new List() {smd}); } } /// /// Performs a p4 add on the specified file /// /// The client path for the file /// public IList MarkFileForAdd(params string[] paths) { Log.TraceFunction(); var change = GetOrCreatePendingChangelist(); var specs = GetEscapedFileSpecs(paths); try { var options = new AddFilesCmdOptions(AddFilesCmdFlags.KeepWildcards, change.Id, null); var result = GetConnection().Client.AddFiles(options, specs); return FixFileSpecs(result); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } /// /// 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) { Log.TraceFunction(); var change = GetOrCreatePendingChangelist(); var specs = GetEscapedFileSpecs(paths); var flags = EditFilesCmdFlags.None; if (serverOnly) { flags = EditFilesCmdFlags.ServerOnly; } try { var options = new EditCmdOptions(flags, change.Id, null); var client = GetConnection().Client; return FixFileSpecs(client.EditFiles(options, specs)); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } // Fix the unescaping of depot paths private IList FixFileSpecs(IList specList) { if (specList != null) { foreach (var f in specList) { f.DepotPath = new DepotPath(EscapePath(f.DepotPath.Path)); } } return specList; } private IList FixFileMetaData(IList metaList) { if (metaList != null) { foreach (var f in metaList) { f.DepotPath = new DepotPath(EscapePath(f.DepotPath.Path)); } } return metaList; } // Escape paths so that Depot paths are always escaped in application. private void EscapeChangelistFilePaths(Changelist change) { if (change != null && change.ShelvedFiles != null) { foreach (var f in change.ShelvedFiles) { f.Path = new DepotPath(EscapePath(f.Path.Path)); } } if (change != null && change.Files != null) { foreach (var f in change.Files) { f.DepotPath = new DepotPath(EscapePath(f.DepotPath.Path)); } } } private void UnescapeChangelistFilePaths(Changelist change) { if (change != null && change.ShelvedFiles != null) { foreach (var f in change.ShelvedFiles) { f.Path = new DepotPath(UnescapePath(f.Path.Path)); } } if (change != null && change.Files != null) { foreach (var f in change.Files) { f.DepotPath = new DepotPath(UnescapePath(f.DepotPath.Path)); } } } /// /// /// /// /// /// public IList RenameFile(string sourcePath, string destPath, bool serverOnly=false) { Log.TraceFunction(); var md = RunFstat(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 = RunFstat(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 = GetEscapedFileSpec(sourcePath); var destFileSpec = GetEscapedFileSpec(destPath); var flags = MoveFileCmdFlags.None; if (serverOnly) { flags = MoveFileCmdFlags.ServerOnly; } try { var options = new MoveCmdOptions(flags, change.Id, null); return FixFileSpecs(GetConnection().Client.MoveFiles(sourceFileSpec, destFileSpec, options)); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } public IList RenameFolder(string sourceDir, string destDir, bool serverOnly = false) { Log.TraceFunction(); // 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 = GetEscapedFileSpec(sourceDirWC); var destFileSpec = GetEscapedFileSpec(destDirWC); CheckoutFiles(serverOnly: true, paths: sourceDirWC); var flags = MoveFileCmdFlags.None; if (serverOnly) { flags = MoveFileCmdFlags.ServerOnly; } try { var options = new MoveCmdOptions(flags, change.Id, null); return FixFileSpecs(GetConnection().Client.MoveFiles(sourceFileSpec, destFileSpec, options)); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } public IList CopyFile(string sourcePath, string destPath, bool serverOnly = false) { Log.TraceFunction(); var md = RunFstat(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 = RunFstat(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 = GetEscapedFileSpec(sourcePath); var destFileSpecList = new List(); destFileSpecList.Add(GetEscapedFileSpec(destPath)); var flags = CopyFilesCmdFlags.None; if(serverOnly) { flags = CopyFilesCmdFlags.Virtual; } try { var options = new CopyFilesCmdOptions(flags, null, null, null, change.Id, 0); return FixFileSpecs(GetConnection().Client.CopyFiles(sourceFileSpec, destFileSpecList, options)); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } /// /// /// /// /// public IList DeleteFiles(bool serverOnly = false, params string[] paths) { Log.TraceFunction(); var change = GetOrCreatePendingChangelist(); var specs = GetEscapedFileSpecs(paths); var flags = DeleteFilesCmdFlags.None; if (serverOnly) { flags = DeleteFilesCmdFlags.ServerOnly; } try { var options = new DeleteFilesCmdOptions(flags, change.Id); return FixFileSpecs(GetConnection().Client.DeleteFiles(options, specs)); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } public IList RevertFiles(bool serverOnly = false, params string[] paths) { Log.TraceFunction(); var change = GetOrCreatePendingChangelist(); var specs = GetEscapedFileSpecs(paths); var flags = RevertFilesCmdFlags.None; if (serverOnly) { flags = RevertFilesCmdFlags.ServerOnly; } var options = new RevertCmdOptions(flags, change.Id); try { var results = GetConnection().Client.RevertFiles(options, specs); return FixFileSpecs(results); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } public P4CommandResult ReconcileFiles(string path, List recArgs = null) { var paths = new List() {path}; return ReconcileFiles(paths.ToArray(), recArgs); } public P4CommandResult ReconcileFiles(string[] paths, List recArgs = null) { Log.TraceFunction(); if (paths == null) return null; var change = GetOrCreatePendingChangelist(); var args = new List(); if (recArgs == null) args.Add("-eadf"); else { args.AddRange(recArgs); } args.Add("-c"); args.Add(change.Id.ToString()); args.AddRange(EscapePaths(paths)); var cmd = new P4Command(GetRepository(), "reconcile", true, args.ToArray()); try { return cmd.Run(); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } public IList ShelveFiles(params string[] paths) { Log.TraceFunction(); var change = GetOrCreatePendingChangelist(); var specs = GetEscapedFileSpecs(paths); var options = new ShelveFilesCmdOptions(ShelveFilesCmdFlags.Force, null, change.Id); try { var results = GetConnection().Client.ShelveFiles(options, specs); return FixFileSpecs(results); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } public IList DeleteShelvedFiles(int changeId = 0, params string[] paths) { Log.TraceFunction(); if (changeId == 0) { var change = GetOrCreatePendingChangelist(); changeId = change.Id; } var specs = GetEscapedFileSpecs(paths); try { var options = new ShelveFilesCmdOptions(ShelveFilesCmdFlags.Delete, null, changeId); return FixFileSpecs(GetConnection().Client.ShelveFiles(options, specs)); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } public List GetShelvedLocations(string depotPath) { Log.TraceFunction(); 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) { Log.TraceFunction(); var shelved = false; var shelveIds = GetShelvedLocations(depotPath); if (shelveIds.Count > 0) { shelved = true; } return shelved; } public IList UnshelveFiles(int changeId = 0, params string[] paths) { Log.TraceFunction(); var change = GetCurrentPendingChangelist(); if (changeId == 0) { changeId = change.Id; } var specs = GetEscapedFileSpecs(paths); var options = new UnshelveFilesCmdOptions(UnshelveFilesCmdFlags.Force, changeId, change.Id); try { var results = GetConnection().Client.UnshelveFiles(options, specs); return FixFileSpecs(results); } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } public bool IsFileOpened(string depotPath) { Log.TraceFunction(); var opened = false; if (!string.IsNullOrEmpty(depotPath)) { var spec = GetEscapedFileSpec(depotPath); var specs = new List(); specs.Add(spec); try { 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; } } } } catch (P4Exception p4e) { LogP4Exception(p4e); } } return opened; } public bool PathHasAnyOpened(string path) { Log.TraceFunction(); var anyOpened = false; if (!string.IsNullOrEmpty(path)) { var results = GetAllOpened(path); 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 path, int max=0) { Log.TraceFunction(); var fileSpecs = new List(); path = AppendWildcard(path, "..."); fileSpecs.Add(GetEscapedFileSpec(EscapePath(path))); var options = new GetOpenedFilesOptions(GetOpenedFilesCmdFlags.AllClients, null, null, null, max); try { var result = GetRepository().GetOpenedFiles(fileSpecs, options); return result; } catch (P4Exception p4e) { LogP4Exception(p4e); return null; } } private List RunCmd(string cmd, string path, List args = null) { return RunCmd(cmd, new List() {path}, args); } private List RunCmd(string cmd, List paths, List args = null) { var results = new List(); P4CommandResult r = null; try { P4Command p4Cmd = new P4Command(GetRepository(), cmd, false, EscapePaths(paths).ToArray()); if (args == null) r = p4Cmd.Run(); else r = p4Cmd.Run(args.ToArray()); } catch (P4Exception) { } if (r != null) { foreach (P4.P4ClientInfoMessage l in r.InfoOutput) { results.Add(l.Message); } } return results; } /// /// Runs fstat on paths /// /// Various sync flags possible, e.g. -f /// Various sync flags possible, e.g. -f /// Whether to report sync errors via UI /// public List RunFstat(List paths, bool escapePaths = true, List fstatArgs = null) { var results = new List(); if (fstatArgs == null) fstatArgs = new List() { "-Oa", "-Oe", "-Ol" }; P4CommandResult r = null; try { List fpaths = paths; if (escapePaths) fpaths = EscapePaths(fpaths); P4Command cmd = new P4Command(GetRepository(), "fstat", true, fpaths.ToArray()); r = cmd.Run(fstatArgs.ToArray()); } catch (P4Exception) {} if (!((r == null) || (r.TaggedOutput == null) || (r.TaggedOutput.Count <= 0))) { foreach (P4.TaggedObject obj in r.TaggedOutput) { if ((obj.Count <= 2) && (obj.ContainsKey("desc"))) { // hack, but this not really a file, it's just a // the description of the change if -e option is // specified, so skip it continue; } var md = new FileMetaData(); md.FromFstatCmdTaggedData(obj); // Fix the stupidity of Fstat which changes depot paths!!!! md.DepotPath = new DepotPath(EscapePath(md.DepotPath.Path)); results.Add(md); } } return results; } public FileMetaData RunFstat(string path, int revision = -1, List fstatArgs = null) { var depotPath = MapToDepot(path); if (depotPath == null) depotPath = path; if (revision != -1) { path = string.Format("{0}#{1}", EscapePath(depotPath), revision); } else { path = EscapePath(depotPath); } var results = RunFstat(new List() {path}, escapePaths: false, fstatArgs: fstatArgs); if (results.Count > 0) return results[0]; return null; } public FileMetaData GetFileMetaData(string path, string attrFilter = null, bool allVersions = false, int maxItems = 0) { Log.TraceFunction(); 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) { Log.TraceFunction(); 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; } if (Utility.IsDepotPath(path)) { specs.Add(new FileSpec(MkUnescapedDepotPath(path), null)); } else { specs.Add(new FileSpec(MkClientPath(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 (P4Exception) { // unable to get metadata -- can happen when bad specs are passed. } return FixFileMetaData(results); } public IList GetSearchFileMetaData(List paths, bool mappedOnly = false) { Log.TraceFunction(); GetFileMetadataCmdFlags flags = GetFileMetadataCmdFlags.Attributes | GetFileMetadataCmdFlags.HexAttributes; IList specs = new List(); foreach(var p in paths) { var fs = new FileSpec(MkUnescapedDepotPath(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 path) { Log.TraceFunction(); SizeData data = null; if (IsDirectory(path) && !path.EndsWith("/...")) { path = path.TrimEnd('/') + "/..."; } var args = new List(); args.Add("-s"); args.Add(path); 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) { Log.TraceFunction(); 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(MkUnescapedDepotPath(depotPath), VersionSpec.None); } else { spec = new FileSpec(MkUnescapedDepotPath(depotPath), VersionSpec.Head); } try { GetRepository().GetFileContents(options, spec); } catch (P4Exception) { } } } return filePath; } public string GetDirectoryFromServer(string depotPath, string targetDir = null) { Log.TraceFunction(); 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 = GetEscapedFileSpec(depotPath + "/..."); var filesOptions = new FilesCmdOptions(FilesCmdFlags.None, 0); try { 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 = GetEscapedFileSpec(fileDepotPath); GetRepository().GetFileContents(getFileContentsOptions, fileSpec); } } catch (P4Exception) { } } return targetDir; } public string GetFileFromShelf(string depotPath, int changeId = -1) { Log.TraceFunction(); 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(MkUnescapedDepotPath(depotPath), shelf); var results = GetRepository().GetFileContents(options, spec); } } return filePath; } public IList GetFileHistory(string path) { Log.TraceFunction(); var flags = GetFileHistoryCmdFlags.IncludeInherited | GetFileHistoryCmdFlags.FullDescription; var fileSpec = GetUnescapedFileSpec(path); // GetFileHistory escapes var options = new GetFileHistoryCmdOptions(flags, 0, 0); try { return GetRepository().GetFileHistory(options, fileSpec); } catch (P4Exception) { } return null; } public SubmitResults RollbackFileToRevision(string path, int revision) { Log.TraceFunction(); var origFileSpec = FileSpec.DepotSpec(UnescapePath(path), revision); var fileSpec = FileSpec.DepotSpec(UnescapePath(path)); 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); if (mdList == null || 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); try { GetConnection().Client.SyncFiles(syncOptions, origFileSpec); } catch (P4Exception) { } // edit the file try { var editOptions = new EditCmdOptions(EditFilesCmdFlags.None, change.Id, null); GetConnection().Client.EditFiles(editOptions, fileSpec); } catch (P4Exception) { } // sync the head revision of the file try { GetConnection().Client.SyncFiles(syncOptions, fileSpec); } catch (P4Exception) { } // resolve the file (ay) try { var resolveOptions = new ResolveCmdOptions(ResolveFilesCmdFlags.AutomaticYoursMode, change.Id); GetConnection().Client.ResolveFiles(resolveOptions, fileSpec); } catch (P4Exception) { } // 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) { var deleteChangelistOptions = new ChangeCmdOptions(ChangeCmdFlags.None); GetRepository().DeleteChangelist(change, deleteChangelistOptions); return null; } } public SubmitResults RollbackFolderToChangelist(string path, int changeId) { Log.TraceFunction(); // 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(MkEscapedDepotPath(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); try { var p4cmdResults = p4cmd.Run(); if (p4cmdResults.TaggedOutput != null) { foreach (TaggedObject obj in p4cmdResults.TaggedOutput) { string depotFile = null; string clientFile = null; var res = obj.TryGetValue("depotFile", out depotFile); res = obj.TryGetValue("clientFile", out clientFile); if (depotFile != null) { var depotFileSpec = GetEscapedFileSpec(depotFile); var clientFileSpec = GetEscapedFileSpec(clientFile); if (obj.ContainsKey("action")) { var action = obj["action"].ToString(); if (action.Equals("deleted")) { deletedList.Add(depotFileSpec); } else if (action.Equals("added")) { addedList.Add(clientFileSpec); } else if (action.Equals("updated")) { updatedList.Add(depotFileSpec); } } } } } } catch (P4Exception) { } // sync to the desired changelist string[] args = { "-f", fileSpec.ToString() }; p4cmd = GetConnection().CreateCommand("sync", true, args); try { var p4cmdResults = p4cmd.Run(); } catch (P4Exception) { } if (updatedList.Count > 0) { // edit the files in the path try { var editOptions = new EditCmdOptions(EditFilesCmdFlags.None, change.Id, null); var editResults = GetConnection().Client.EditFiles(editOptions, updatedList.ToArray()); } catch (P4Exception) { } // sync the head revision of the file try { var syncOptions1 = new SyncFilesCmdOptions(SyncFilesCmdFlags.Force, 0); var syncResults2 = GetConnection().Client.SyncFiles(syncOptions1, updatedList.ToArray()); } catch (P4Exception) { } // resolve the file (ay) try { var resolveOptions = new ResolveCmdOptions(ResolveFilesCmdFlags.AutomaticYoursMode, change.Id); var resolveResults = GetConnection().Client.ResolveFiles(resolveOptions, updatedList.ToArray()); } catch (P4Exception) { } } if (addedList.Count > 0) { // find any deleted items that need to be re-added with the -d (downgrade) flag try { // AddFilesCmdFlags.Downgrade | var addOptions = new AddFilesCmdOptions(AddFilesCmdFlags.KeepWildcards, change.Id, null); var results = GetConnection().Client.AddFiles(addOptions, addedList.ToArray()); } catch (P4Exception) { } } if (deletedList.Count > 0) { // delete any items that aren't supposed to be there try { var deleteOptions = new DeleteFilesCmdOptions(DeleteFilesCmdFlags.DeleteUnsynced, change.Id); var results = GetConnection().Client.DeleteFiles(deleteOptions, deletedList.ToArray()); } catch (P4Exception) { } } // submit the changes var clientSubmitOptions = new ClientSubmitOptions(); clientSubmitOptions.SubmitType = SubmitType.SubmitUnchanged; try { var submitOptions = new SubmitCmdOptions(SubmitFilesCmdFlags.None, change.Id, null, null, clientSubmitOptions); 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); } } public P4CommandResult RunCmd(string cmd, bool tagged, string[] args) { var p4Cmd = GetConnection(true).CreateCommand(cmd, tagged, args); return p4Cmd.Run(); } #endregion #region CONSTRUCTORS public PerforceHelper(string serverUri, string username) { Log.TraceFunction(); _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); } repo.Connection.InfoResultsReceived += CommandLine_InfoResultsCallbackFn; repo.Connection.ErrorReceived += CommandLine_ErrorCallbackFn; repo.Connection.TextResultsReceived += CommandLine_TextResultsCallbackFn; repo.Connection.TaggedOutputReceived += CommandLine_TaggedOutputCallbackFn; repo.Connection.CommandEcho += CommandLine_CommandEchoCallbackFn; } 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; } private WorkspaceWatcher GetWorkspaceWatcher() { if (TestWorkspaceWatcher != null) return TestWorkspaceWatcher; return Utility.GetWorkspaceWatcher(); } #endregion #region Logging stuff static string[] ErrorSeverity = new string[] { "Empty", "Info", "Warning", "Failed", "Fatal" }; static int[] MessageSeverity = new int[] { 4, //"E_EMPTY", 3, //"E_INFO", 2, //"E_WARN", 1, //"E_FAILED", 0 //"E_FATAL", }; private void CommandLine_ErrorCallbackFn(uint cmdId, int severity, int errorNumber, string data) { try { string severityStr = string.Empty; if ((severity < 0) || (severity >= ErrorSeverity.Length)) { severityStr = string.Format("E_{0}", severity); } else { severityStr = ErrorSeverity[severity]; } string msg = string.Format("{0}: {1}", severityStr, data); if (_log.IsDebugEnabled) { } if ((severity < 0) || (severity >= ErrorSeverity.Length)) { LoggingFunction(0, "P4API.NET", msg); } else { LoggingFunction(MessageSeverity[severity], "P4API.NET", msg); } } catch (Exception ex) { string msg = string.Format("Error trying to log message: [{0}]: {1}", severity, data); LoggingFunction(0, "P4API.NET", msg); Log.Exception(ex); } } private void CommandLine_InfoResultsCallbackFn(uint cmdId, int msgId, int level, string data) { if (_log.IsDebugEnabled) { // level is generally from 0-9, though for some reason trying to add an // ignored file sends a message with a level of 34, so ignor level of 34 if (level == 34) { level = 0; } try { string msg = "--->"; for (int idx = 0; idx < level; idx++) { msg += ". "; } msg += (data != null) ? data : string.Empty; LoggingFunction(3, "P4API.NET", msg); } catch (Exception ex) { Log.Exception(ex); } } } private void CommandLine_TaggedOutputCallbackFn(uint cmdId, int ObjId, P4.TaggedObject Obj) { if (_log.IsDebugEnabled) { try { if (Obj == null) { return; } string msg = "--->Tagged Data: { "; foreach (string key in Obj.Keys) { msg += string.Format("{{{0}:{1}}} ", key, Obj[key]); } msg += "}"; LoggingFunction(3, "P4API.NET", msg); } catch (Exception ex) { Log.Exception(ex); } } } private void CommandLine_TextResultsCallbackFn(uint cmdId, string data) { if (_log.IsDebugEnabled) { string msg = string.Format("->{0}", data); LoggingFunction(3, "P4API.NET", msg); } } private void CommandLine_CommandEchoCallbackFn(string data) { if (data.StartsWith("DataSet set to:")) { // echoing the commands data set, record this only to the log file. LoggingFunction(3, "P4API.NET", data); return; } string[] cmds = data.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); string cmd = cmds[0]; bool log = false; switch (cmd) { case "depot": case "client": case "workspace": case "user": case "group": case "changelist": case "change": case "job": case "branch": case "label": case "remote": case "stream": case "server": case "protect": case "triggers": if ((data.IndexOf(" -i ") > 0) || (data.EndsWith(" -i")) || (data.IndexOf(" -d ") > 0) || (data.EndsWith(" -d"))) { log = true; } break; default: log = true; break; } if (log && _log.IsDebugEnabled) { string msg = string.Format("->{0}", data); LoggingFunction(3, "P4API.NET", msg); return; } } #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 }