Example #1
0
        public Workspace(PerforceConnection InPerforce, string InLocalRootPath, string InSelectedLocalFileName, string InClientRootPath, string InSelectedClientFileName, int InInitialChangeNumber, int InLastBuiltChangeNumber, string InTelemetryProjectPath, TextWriter InLog)
        {
            Perforce               = InPerforce;
            LocalRootPath          = InLocalRootPath;
            SelectedLocalFileName  = InSelectedLocalFileName;
            ClientRootPath         = InClientRootPath;
            SelectedClientFileName = InSelectedClientFileName;
            CurrentChangeNumber    = InInitialChangeNumber;
            PendingChangeNumber    = InInitialChangeNumber;
            LastBuiltChangeNumber  = InLastBuiltChangeNumber;
            TelemetryProjectPath   = InTelemetryProjectPath;
            Log = InLog;

            List <string> SyncPaths = new List <string>();

            if (SelectedClientFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
            {
                SyncPaths.Add(ClientRootPath + "/*");
                SyncPaths.Add(ClientRootPath + "/Engine/...");
                SyncPaths.Add(PerforceUtils.GetClientOrDepotDirectoryName(SelectedClientFileName) + "/...");
            }
            else
            {
                SyncPaths.Add(ClientRootPath + "/...");
            }
            this.SyncPaths = SyncPaths.AsReadOnly();

            ProjectConfigFile = ReadProjectConfigFile(InLocalRootPath, InSelectedLocalFileName, Log);
        }
Example #2
0
        void PollForUpdates()
        {
            string StreamName;

            if (!Perforce.GetActiveStream(out StreamName, LogWriter))
            {
                StreamName = null;
            }

            // Try to update the zipped binaries list before anything else, because it causes a state change in the UI
            UpdateZippedBinaries();

            while (!bDisposing)
            {
                Stopwatch Timer = Stopwatch.StartNew();

                // Check we haven't switched streams
                string NewStreamName;
                if (Perforce.GetActiveStream(out NewStreamName, LogWriter) && NewStreamName != StreamName)
                {
                    OnStreamChange();
                }

                // Update the stream list
                if (StreamName != null)
                {
                    List <string> NewOtherStreamNames;
                    if (!Perforce.FindStreams(PerforceUtils.GetClientOrDepotDirectoryName(StreamName) + "/*", out NewOtherStreamNames, LogWriter))
                    {
                        NewOtherStreamNames = new List <string>();
                    }
                    OtherStreamNames = NewOtherStreamNames;
                }

                // Check for any p4 changes
                if (!UpdateChanges())
                {
                    LastStatusMessage = "Failed to update changes";
                }
                else if (!UpdateChangeTypes())
                {
                    LastStatusMessage = "Failed to update change types";
                }
                else if (!UpdateZippedBinaries())
                {
                    LastStatusMessage = "Failed to update zipped binaries list";
                }
                else
                {
                    LastStatusMessage = String.Format("Last update took {0}ms", Timer.ElapsedMilliseconds);
                }

                // Wait for another request, or scan for new builds after a timeout
                RefreshEvent.WaitOne(60 * 1000);
            }
        }
        public bool Run(out string ErrorMessage)
        {
            StringBuilder FailMessage = new StringBuilder();

            if (FilesToSync.Count > 0)
            {
                List <string> RevisionsToSync = new List <string>();
                foreach (FileInfo FileToSync in FilesToSync)
                {
                    RevisionsToSync.Add(String.Format("{0}#have", PerforceUtils.EscapePath(FileToSync.FullName)));
                }

                StringWriter Log = new StringWriter();
                if (!Perforce.Sync(RevisionsToSync, x => { }, new List <string>(), true, null, Log))
                {
                    FailMessage.Append(Log.ToString());
                }
            }

            foreach (FileInfo FileToDelete in FilesToDelete)
            {
                try
                {
                    FileToDelete.IsReadOnly = false;
                    FileToDelete.Delete();
                }
                catch (Exception Ex)
                {
                    FailMessage.AppendFormat("{0} ({1})\r\n", FileToDelete.FullName, Ex.Message.Trim());
                }
            }
            foreach (DirectoryInfo DirectoryToDelete in DirectoriesToDelete)
            {
                try
                {
                    DirectoryToDelete.Delete(true);
                }
                catch (Exception Ex)
                {
                    FailMessage.AppendFormat("{0} ({1})\r\n", DirectoryToDelete.FullName, Ex.Message.Trim());
                }
            }

            if (FailMessage.Length == 0)
            {
                ErrorMessage = null;
                return(true);
            }
            else
            {
                ErrorMessage = FailMessage.ToString();
                return(false);
            }
        }
        public bool Run(PerforceConnection Perforce, TextWriter Log, out string ErrorMessage)
        {
            // Use the cached client path to the file if it's available; it's much quicker than trying to find the correct workspace.
            if (!String.IsNullOrEmpty(SelectedProject.ClientPath))
            {
                // Get the client path
                NewSelectedClientFileName = SelectedProject.ClientPath;

                // Get the client name
                string ClientName;
                if (!PerforceUtils.TryGetClientName(NewSelectedClientFileName, out ClientName))
                {
                    ErrorMessage = String.Format("Couldn't get client name from {0}", NewSelectedClientFileName);
                    return(false);
                }

                // Create the client
                PerforceClient = new PerforceConnection(Perforce.UserName, ClientName, Perforce.ServerAndPort);

                // Figure out the path on the client. Use the cached location if it's valid.
                if (SelectedProject.LocalPath != null && File.Exists(SelectedProject.LocalPath))
                {
                    NewSelectedFileName = SelectedProject.LocalPath;
                }
                else
                {
                    if (!PerforceClient.ConvertToLocalPath(NewSelectedClientFileName, out NewSelectedFileName, Log))
                    {
                        ErrorMessage = String.Format("Couldn't get client path for {0}", NewSelectedClientFileName);
                        return(false);
                    }
                }
            }
            else
            {
                // Get the perforce server settings
                PerforceInfoRecord PerforceInfo;
                if (!Perforce.Info(out PerforceInfo, Log))
                {
                    ErrorMessage = String.Format("Couldn't get Perforce server info");
                    return(false);
                }

                // Use the path as the selected filename
                NewSelectedFileName = SelectedProject.LocalPath;

                // Make sure the project exists
                if (!File.Exists(SelectedProject.LocalPath))
                {
                    ErrorMessage = String.Format("{0} does not exist.", SelectedProject.LocalPath);
                    return(false);
                }

                // Find all the clients for this user
                Log.WriteLine("Enumerating clients for {0}...", PerforceInfo.UserName);

                List <PerforceClientRecord> Clients;
                if (!Perforce.FindClients(PerforceInfo.UserName, out Clients, Log))
                {
                    ErrorMessage = String.Format("Couldn't find any clients for this host.");
                    return(false);
                }

                List <PerforceConnection> CandidateClients = FilterClients(Clients, Perforce.ServerAndPort, PerforceInfo.HostName, PerforceInfo.UserName);
                if (CandidateClients.Count == 0)
                {
                    // Search through all workspaces. We may find a suitable workspace which is for any user.
                    Log.WriteLine("Enumerating shared clients...");
                    if (!Perforce.FindClients("", out Clients, Log))
                    {
                        ErrorMessage = "Failed to enumerate clients.";
                        return(false);
                    }

                    // Filter this list of clients
                    CandidateClients = FilterClients(Clients, Perforce.ServerAndPort, PerforceInfo.HostName, PerforceInfo.UserName);

                    // If we still couldn't find any, fail.
                    if (CandidateClients.Count == 0)
                    {
                        ErrorMessage = String.Format("Couldn't find any Perforce workspace containing {0}. Check your connection settings.", NewSelectedFileName);
                        return(false);
                    }
                }

                // Check there's only one client
                if (CandidateClients.Count > 1)
                {
                    ErrorMessage = String.Format("Found multiple workspaces containing {0}:\n\n{1}\n\nCannot determine which to use.", Path.GetFileName(NewSelectedFileName), String.Join("\n", CandidateClients.Select(x => x.ClientName)));
                    return(false);
                }

                // Take the client we've chosen
                PerforceClient = CandidateClients[0];

                // Get the client path for the project file
                if (!PerforceClient.ConvertToClientPath(NewSelectedFileName, out NewSelectedClientFileName, Log))
                {
                    ErrorMessage = String.Format("Couldn't get client path for {0}", NewSelectedFileName);
                    return(false);
                }
            }

            // Normalize the filename
            NewSelectedFileName = Path.GetFullPath(NewSelectedFileName).Replace('/', Path.DirectorySeparatorChar);

            // Make sure the path case is correct. This can cause UBT intermediates to be out of date if the case mismatches.
            NewSelectedFileName = Utility.GetPathWithCorrectCase(new FileInfo(NewSelectedFileName));

            // Update the selected project with all the data we've found
            SelectedProject = new UserSelectedProjectSettings(SelectedProject.ServerAndPort, SelectedProject.UserName, SelectedProject.Type, NewSelectedClientFileName, NewSelectedFileName);

            // Figure out where the engine is in relation to it
            int EndIdx = NewSelectedClientFileName.Length - 1;

            if (EndIdx != -1 && NewSelectedClientFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
            {
                EndIdx = NewSelectedClientFileName.LastIndexOf('/') - 1;
            }
            for (;; EndIdx--)
            {
                if (EndIdx < 2)
                {
                    ErrorMessage = String.Format("Could not find engine in Perforce relative to project path ({0})", NewSelectedClientFileName);
                    return(false);
                }
                if (NewSelectedClientFileName[EndIdx] == '/')
                {
                    List <PerforceFileRecord> FileRecords;
                    if (PerforceClient.Stat(NewSelectedClientFileName.Substring(0, EndIdx) + "/Engine/Build/Build.version", out FileRecords, Log) && FileRecords.Count > 0)
                    {
                        if (FileRecords[0].ClientPath == null)
                        {
                            ErrorMessage = String.Format("Missing client path for {0}", FileRecords[0].DepotPath);
                            return(false);
                        }

                        BranchClientPath    = NewSelectedClientFileName.Substring(0, EndIdx);
                        BranchDirectoryName = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(FileRecords[0].ClientPath), "..", ".."));
                        break;
                    }
                }
            }
            Log.WriteLine("Found branch root at {0}", BranchClientPath);

            // Find the editor target for this project
            if (NewSelectedFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
            {
                List <PerforceFileRecord> Files;
                if (PerforceClient.FindFiles(PerforceUtils.GetClientOrDepotDirectoryName(NewSelectedClientFileName) + "/Source/*Editor.Target.cs", out Files, Log) && Files.Count >= 1)
                {
                    string DepotPath = Files[0].DepotPath;
                    NewProjectEditorTarget = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(DepotPath.Substring(DepotPath.LastIndexOf('/') + 1)));
                    Log.WriteLine("Using {0} as editor target name (from {1})", NewProjectEditorTarget, Files[0]);
                }
                if (NewProjectEditorTarget == null)
                {
                    Log.WriteLine("Couldn't find any editor targets for this project.");
                }
            }

            // Get a unique name for the project that's selected. For regular branches, this can be the depot path. For streams, we want to include the stream name to encode imports.
            if (PerforceClient.GetActiveStream(out StreamName, Log))
            {
                string ExpectedPrefix = String.Format("//{0}/", PerforceClient.ClientName);
                if (!NewSelectedClientFileName.StartsWith(ExpectedPrefix, StringComparison.InvariantCultureIgnoreCase))
                {
                    ErrorMessage = String.Format("Unexpected client path; expected '{0}' to begin with '{1}'", NewSelectedClientFileName, ExpectedPrefix);
                    return(false);
                }
                string StreamPrefix;
                if (!TryGetStreamPrefix(PerforceClient, StreamName, Log, out StreamPrefix))
                {
                    ErrorMessage = String.Format("Failed to get stream info for {0}", StreamName);
                    return(false);
                }
                NewSelectedProjectIdentifier = String.Format("{0}/{1}", StreamPrefix, NewSelectedClientFileName.Substring(ExpectedPrefix.Length));
            }
            else
            {
                if (!PerforceClient.ConvertToDepotPath(NewSelectedClientFileName, out NewSelectedProjectIdentifier, Log))
                {
                    ErrorMessage = String.Format("Couldn't get depot path for {0}", NewSelectedFileName);
                    return(false);
                }
            }

            // Read the project logo
            if (NewSelectedFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
            {
                string LogoFileName = Path.Combine(Path.GetDirectoryName(NewSelectedFileName), "Build", "UnrealGameSync.png");
                if (File.Exists(LogoFileName))
                {
                    try
                    {
                        // Duplicate the image, otherwise we'll leave the file locked
                        using (Image Image = Image.FromFile(LogoFileName))
                        {
                            ProjectLogo = new Bitmap(Image);
                        }
                    }
                    catch
                    {
                        ProjectLogo = null;
                    }
                }
            }

            // Figure out if it's an enterprise project. Use the synced version if we have it.
            if (NewSelectedClientFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
            {
                string Text;
                if (File.Exists(NewSelectedFileName))
                {
                    Text = File.ReadAllText(NewSelectedFileName);
                }
                else
                {
                    List <string> ProjectLines;
                    if (!PerforceClient.Print(NewSelectedClientFileName, out ProjectLines, Log))
                    {
                        ErrorMessage = String.Format("Unable to get contents of {0}", NewSelectedClientFileName);
                        return(false);
                    }
                    Text = String.Join("\n", ProjectLines);
                }
                bIsEnterpriseProject = Utility.IsEnterpriseProjectFromText(Text);
            }

            // Read the initial config file
            LocalConfigFiles        = new List <KeyValuePair <string, DateTime> >();
            LatestProjectConfigFile = PerforceMonitor.ReadProjectConfigFile(PerforceClient, BranchClientPath, NewSelectedClientFileName, CacheFolder, LocalConfigFiles, Log);

            // Succeed!
            ErrorMessage = null;
            return(true);
        }
        public bool Run(out string ErrorMessage)
        {
            Log.WriteLine("Finding files in workspace...");
            Log.WriteLine();

            // Start enumerating all the files that exist locally
            foreach (string SyncPath in SyncPaths)
            {
                Debug.Assert(SyncPath.StartsWith(ClientRootPath));
                if (SyncPath.StartsWith(ClientRootPath, StringComparison.InvariantCultureIgnoreCase))
                {
                    string[] Fragments = SyncPath.Substring(ClientRootPath.Length).Split('/');

                    FolderToClean SyncFolder = RootFolderToClean;
                    for (int Idx = 0; Idx < Fragments.Length - 1; Idx++)
                    {
                        FolderToClean NextSyncFolder;
                        if (!SyncFolder.NameToSubFolder.TryGetValue(Fragments[Idx], out NextSyncFolder))
                        {
                            NextSyncFolder = new FolderToClean(new DirectoryInfo(Path.Combine(SyncFolder.Directory.FullName, Fragments[Idx])));
                            SyncFolder.NameToSubFolder[NextSyncFolder.Name] = NextSyncFolder;
                        }
                        SyncFolder = NextSyncFolder;
                    }

                    string Wildcard = Fragments[Fragments.Length - 1];
                    if (Wildcard == "...")
                    {
                        QueueFolderToPopulate(SyncFolder);
                    }
                    else
                    {
                        if (SyncFolder.Directory.Exists)
                        {
                            foreach (FileInfo File in SyncFolder.Directory.EnumerateFiles(Wildcard))
                            {
                                SyncFolder.NameToFile[File.Name] = File;
                            }
                        }
                    }
                }
            }

            // Get the prefix for any local file
            string LocalRootPrefix = RootFolderToClean.Directory.FullName.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;

            // Query the have table and build a separate tree from it
            PerforceHaveFolder RootHaveFolder = new PerforceHaveFolder();

            foreach (string SyncPath in SyncPaths)
            {
                List <PerforceFileRecord> FileRecords;
                if (!PerforceClient.Stat(String.Format("{0}#have", SyncPath), out FileRecords, Log))
                {
                    ErrorMessage = "Couldn't query have table from Perforce.";
                    return(false);
                }
                foreach (PerforceFileRecord FileRecord in FileRecords)
                {
                    if (!FileRecord.ClientPath.StartsWith(LocalRootPrefix, StringComparison.InvariantCultureIgnoreCase))
                    {
                        ErrorMessage = String.Format("Failed to get have table; file '{0}' doesn't start with root path ('{1}')", FileRecord.ClientPath, RootFolderToClean.Directory.FullName);
                        return(false);
                    }

                    string[] Tokens = FileRecord.ClientPath.Substring(LocalRootPrefix.Length).Split('/', '\\');

                    PerforceHaveFolder FileFolder = RootHaveFolder;
                    for (int Idx = 0; Idx < Tokens.Length - 1; Idx++)
                    {
                        PerforceHaveFolder NextFileFolder;
                        if (!FileFolder.NameToSubFolder.TryGetValue(Tokens[Idx], out NextFileFolder))
                        {
                            NextFileFolder = new PerforceHaveFolder();
                            FileFolder.NameToSubFolder.Add(Tokens[Idx], NextFileFolder);
                        }
                        FileFolder = NextFileFolder;
                    }
                    FileFolder.NameToFile[Tokens[Tokens.Length - 1]] = FileRecord;
                }
            }

            // Find all the files which are currently open for edit. We don't want to force sync these.
            List <PerforceFileRecord> OpenFileRecords;

            if (!PerforceClient.GetOpenFiles("//...", out OpenFileRecords, Log))
            {
                ErrorMessage = "Couldn't query open files from Perforce.";
                return(false);
            }

            // Build a set of all the open local files
            HashSet <string> OpenLocalFiles = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase);

            foreach (PerforceFileRecord OpenFileRecord in OpenFileRecords)
            {
                if (!OpenFileRecord.ClientPath.StartsWith(ClientRootPath, StringComparison.InvariantCultureIgnoreCase))
                {
                    ErrorMessage = String.Format("Failed to get open file list; file '{0}' doesn't start with client root path ('{1}')", OpenFileRecord.ClientPath, ClientRootPath);
                    return(false);
                }
                OpenLocalFiles.Add(LocalRootPrefix + PerforceUtils.UnescapePath(OpenFileRecord.ClientPath).Substring(ClientRootPath.Length).Replace('/', Path.DirectorySeparatorChar));
            }

            // Wait to finish scanning the directory
            FinishedScan.WaitOne();

            // Find the value of the P4CONFIG variable
            string PerforceConfigFile;

            PerforceClient.GetSetting("P4CONFIG", out PerforceConfigFile, Log);

            // Merge the trees
            MergeTrees(RootFolderToClean, RootHaveFolder, OpenLocalFiles, PerforceConfigFile);

            // Remove all the empty folders
            RemoveEmptyFolders(RootFolderToClean);
            ErrorMessage = null;
            return(true);
        }
Example #6
0
        bool UpdateZippedBinaries()
        {
            // Get the path to the config file
            string ClientConfigFileName = PerforceUtils.GetClientOrDepotDirectoryName(SelectedClientFileName) + "/Build/UnrealGameSync.ini";

            // Find the most recent change to that file (if the file doesn't exist, we succeed and just get 0 changes).
            List <PerforceChangeSummary> ConfigFileChanges;

            if (!Perforce.FindChanges(ClientConfigFileName, 1, out ConfigFileChanges, LogWriter))
            {
                return(false);
            }

            // Update the zipped binaries path if it's changed
            int NewZippedBinariesConfigChangeNumber = (ConfigFileChanges.Count > 0)? ConfigFileChanges[0].Number : 0;

            if (NewZippedBinariesConfigChangeNumber != ZippedBinariesConfigChangeNumber)
            {
                string NewZippedBinariesPath = null;
                if (NewZippedBinariesConfigChangeNumber != 0)
                {
                    List <string> Lines;
                    if (!Perforce.Print(ClientConfigFileName, out Lines, LogWriter))
                    {
                        return(false);
                    }

                    ConfigFile NewConfigFile = new ConfigFile();
                    NewConfigFile.Parse(Lines.ToArray());

                    ConfigSection ProjectSection = NewConfigFile.FindSection(SelectedProjectIdentifier);
                    if (ProjectSection != null)
                    {
                        NewZippedBinariesPath = ProjectSection.GetValue("ZippedBinariesPath", null);
                    }
                }
                ZippedBinariesPath = NewZippedBinariesPath;
                ZippedBinariesConfigChangeNumber = NewZippedBinariesConfigChangeNumber;
            }

            SortedList <int, string> NewChangeNumberToZippedBinaries = new SortedList <int, string>();

            if (ZippedBinariesPath != null)
            {
                List <PerforceFileChangeSummary> Changes;
                if (!Perforce.FindFileChanges(ZippedBinariesPath, 100, out Changes, LogWriter))
                {
                    return(false);
                }

                foreach (PerforceFileChangeSummary Change in Changes)
                {
                    if (Change.Action != "purge")
                    {
                        string[] Tokens = Change.Description.Split(' ');
                        if (Tokens[0].StartsWith("[CL") && Tokens[1].EndsWith("]"))
                        {
                            int OriginalChangeNumber;
                            if (int.TryParse(Tokens[1].Substring(0, Tokens[1].Length - 1), out OriginalChangeNumber) && !NewChangeNumberToZippedBinaries.ContainsKey(OriginalChangeNumber))
                            {
                                NewChangeNumberToZippedBinaries[OriginalChangeNumber] = String.Format("{0}#{1}", ZippedBinariesPath, Change.Revision);
                            }
                        }
                    }
                }
            }

            if (!ChangeNumberToZippedBinaries.SequenceEqual(NewChangeNumberToZippedBinaries))
            {
                ChangeNumberToZippedBinaries = NewChangeNumberToZippedBinaries;
                if (OnUpdateMetadata != null && Changes.Count > 0)
                {
                    OnUpdateMetadata();
                }
            }

            return(true);
        }
Example #7
0
        bool UpdateChanges()
        {
            // Get the current status of the build
            int             MaxChanges;
            int             OldestChangeNumber = -1;
            int             NewestChangeNumber = -1;
            HashSet <int>   CurrentChangelists;
            SortedSet <int> PrevPromotedChangelists;

            lock (this)
            {
                MaxChanges = PendingMaxChanges;
                if (Changes.Count > 0)
                {
                    NewestChangeNumber = Changes.First().Number;
                    OldestChangeNumber = Changes.Last().Number;
                }
                CurrentChangelists      = new HashSet <int>(Changes.Select(x => x.Number));
                PrevPromotedChangelists = new SortedSet <int>(PromotedChangeNumbers);
            }

            // Build a full list of all the paths to sync
            List <string> DepotPaths = new List <string>();

            if (SelectedClientFileName.EndsWith(".uprojectdirs", StringComparison.InvariantCultureIgnoreCase))
            {
                DepotPaths.Add(String.Format("{0}/...", BranchClientPath));
            }
            else
            {
                DepotPaths.Add(String.Format("{0}/*", BranchClientPath));
                DepotPaths.Add(String.Format("{0}/Engine/...", BranchClientPath));
                DepotPaths.Add(String.Format("{0}/...", PerforceUtils.GetClientOrDepotDirectoryName(SelectedClientFileName)));
            }

            // Read any new changes
            List <PerforceChangeSummary> NewChanges;

            if (MaxChanges > CurrentMaxChanges)
            {
                if (!Perforce.FindChanges(DepotPaths, MaxChanges, out NewChanges, LogWriter))
                {
                    return(false);
                }
            }
            else
            {
                if (!Perforce.FindChanges(DepotPaths.Select(DepotPath => String.Format("{0}@>{1}", DepotPath, NewestChangeNumber)), -1, out NewChanges, LogWriter))
                {
                    return(false);
                }
            }

            // Remove anything we already have
            NewChanges.RemoveAll(x => CurrentChangelists.Contains(x.Number));

            // Update the change ranges
            if (NewChanges.Count > 0)
            {
                OldestChangeNumber = Math.Max(OldestChangeNumber, NewChanges.Last().Number);
                NewestChangeNumber = Math.Min(NewestChangeNumber, NewChanges.First().Number);
            }

            // Fixup any ROBOMERGE authors
            const string RoboMergePrefix = "#ROBOMERGE-AUTHOR:";

            foreach (PerforceChangeSummary Change in NewChanges)
            {
                if (Change.Description.StartsWith(RoboMergePrefix))
                {
                    int StartIdx = RoboMergePrefix.Length;
                    while (StartIdx < Change.Description.Length && Change.Description[StartIdx] == ' ')
                    {
                        StartIdx++;
                    }

                    int EndIdx = StartIdx;
                    while (EndIdx < Change.Description.Length && !Char.IsWhiteSpace(Change.Description[EndIdx]))
                    {
                        EndIdx++;
                    }

                    if (EndIdx > StartIdx)
                    {
                        Change.User        = Change.Description.Substring(StartIdx, EndIdx - StartIdx);
                        Change.Description = "ROBOMERGE: " + Change.Description.Substring(EndIdx).TrimStart();
                    }
                }
            }

            // Process the new changes received
            if (NewChanges.Count > 0 || MaxChanges < CurrentMaxChanges)
            {
                // Insert them into the builds list
                lock (this)
                {
                    Changes.UnionWith(NewChanges);
                    while (Changes.Count > MaxChanges)
                    {
                        Changes.Remove(Changes.Last());
                    }
                    CurrentMaxChanges = MaxChanges;
                }

                // Find the last submitted change by the current user
                int NewLastChangeByCurrentUser = -1;
                foreach (PerforceChangeSummary Change in Changes)
                {
                    if (String.Compare(Change.User, Perforce.UserName, StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        NewLastChangeByCurrentUser = Math.Max(NewLastChangeByCurrentUser, Change.Number);
                    }
                }
                LastChangeByCurrentUser = NewLastChangeByCurrentUser;

                // Notify the main window that we've got more data
                if (OnUpdate != null)
                {
                    OnUpdate();
                }
            }
            return(true);
        }
Example #8
0
        bool UpdateChanges()
        {
            // Get the current status of the build
            int             MaxChanges;
            int             OldestChangeNumber = -1;
            int             NewestChangeNumber = -1;
            HashSet <int>   CurrentChangelists;
            SortedSet <int> PrevPromotedChangelists;

            lock (this)
            {
                MaxChanges = PendingMaxChanges;
                if (Changes.Count > 0)
                {
                    NewestChangeNumber = Changes.First().Number;
                    OldestChangeNumber = Changes.Last().Number;
                }
                CurrentChangelists      = new HashSet <int>(Changes.Select(x => x.Number));
                PrevPromotedChangelists = new SortedSet <int>(PromotedChangeNumbers);
            }

            // Build a full list of all the paths to sync
            List <string> DepotPaths = new List <string>();

            if (SelectedClientFileName.EndsWith(".uprojectdirs", StringComparison.InvariantCultureIgnoreCase))
            {
                DepotPaths.Add(String.Format("{0}/...", BranchClientPath));
            }
            else
            {
                DepotPaths.Add(String.Format("{0}/*", BranchClientPath));
                DepotPaths.Add(String.Format("{0}/Engine/...", BranchClientPath));
                DepotPaths.Add(String.Format("{0}/...", PerforceUtils.GetClientOrDepotDirectoryName(SelectedClientFileName)));
                if (bIsEnterpriseProject)
                {
                    DepotPaths.Add(String.Format("{0}/Enterprise/...", BranchClientPath));
                }
            }

            // Read any new changes
            List <PerforceChangeSummary> NewChanges;

            if (MaxChanges > CurrentMaxChanges)
            {
                if (!Perforce.FindChanges(DepotPaths, MaxChanges, out NewChanges, LogWriter))
                {
                    return(false);
                }
            }
            else
            {
                if (!Perforce.FindChanges(DepotPaths.Select(DepotPath => String.Format("{0}@>{1}", DepotPath, NewestChangeNumber)), -1, out NewChanges, LogWriter))
                {
                    return(false);
                }
            }

            // Remove anything we already have
            NewChanges.RemoveAll(x => CurrentChangelists.Contains(x.Number));

            // Update the change ranges
            if (NewChanges.Count > 0)
            {
                OldestChangeNumber = Math.Max(OldestChangeNumber, NewChanges.Last().Number);
                NewestChangeNumber = Math.Min(NewestChangeNumber, NewChanges.First().Number);
            }

            // If we are using zipped binaries, make sure we have every change since the last zip containing them. This is necessary for ensuring that content changes show as
            // syncable in the workspace view if there have been a large number of content changes since the last code change.
            int MinZippedChangeNumber = -1;

            foreach (int ChangeNumber in ChangeNumberToZippedBinaries.Keys)
            {
                if (ChangeNumber > MinZippedChangeNumber && ChangeNumber <= OldestChangeNumber)
                {
                    MinZippedChangeNumber = ChangeNumber;
                }
            }
            if (MinZippedChangeNumber != -1 && MinZippedChangeNumber < OldestChangeNumber)
            {
                List <PerforceChangeSummary> ZipChanges;
                if (Perforce.FindChanges(DepotPaths.Select(DepotPath => String.Format("{0}@{1},{2}", DepotPath, MinZippedChangeNumber, OldestChangeNumber - 1)), -1, out ZipChanges, LogWriter))
                {
                    NewChanges.AddRange(ZipChanges);
                }
            }

            // Fixup any ROBOMERGE authors
            const string RoboMergePrefix = "#ROBOMERGE-AUTHOR:";

            foreach (PerforceChangeSummary Change in NewChanges)
            {
                if (Change.Description.StartsWith(RoboMergePrefix))
                {
                    int StartIdx = RoboMergePrefix.Length;
                    while (StartIdx < Change.Description.Length && Change.Description[StartIdx] == ' ')
                    {
                        StartIdx++;
                    }

                    int EndIdx = StartIdx;
                    while (EndIdx < Change.Description.Length && !Char.IsWhiteSpace(Change.Description[EndIdx]))
                    {
                        EndIdx++;
                    }

                    if (EndIdx > StartIdx)
                    {
                        Change.User        = Change.Description.Substring(StartIdx, EndIdx - StartIdx);
                        Change.Description = "ROBOMERGE: " + Change.Description.Substring(EndIdx).TrimStart();
                    }
                }
            }

            // Process the new changes received
            if (NewChanges.Count > 0 || MaxChanges < CurrentMaxChanges)
            {
                // Insert them into the builds list
                lock (this)
                {
                    Changes.UnionWith(NewChanges);
                    if (Changes.Count > MaxChanges)
                    {
                        // Remove changes to shrink it to the max requested size, being careful to avoid removing changes that would affect our ability to correctly
                        // show the availability for content changes using zipped binaries.
                        SortedSet <PerforceChangeSummary> TrimmedChanges = new SortedSet <PerforceChangeSummary>(new PerforceChangeSorter());
                        foreach (PerforceChangeSummary Change in Changes)
                        {
                            TrimmedChanges.Add(Change);
                            if (TrimmedChanges.Count >= MaxChanges && (ChangeNumberToZippedBinaries.Count == 0 || ChangeNumberToZippedBinaries.ContainsKey(Change.Number) || ChangeNumberToZippedBinaries.First().Key > Change.Number))
                            {
                                break;
                            }
                        }
                        Changes = TrimmedChanges;
                    }
                    CurrentMaxChanges = MaxChanges;
                }

                // Find the last submitted change by the current user
                int NewLastChangeByCurrentUser = -1;
                foreach (PerforceChangeSummary Change in Changes)
                {
                    if (String.Compare(Change.User, Perforce.UserName, StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        NewLastChangeByCurrentUser = Math.Max(NewLastChangeByCurrentUser, Change.Number);
                    }
                }
                LastChangeByCurrentUser = NewLastChangeByCurrentUser;

                // Notify the main window that we've got more data
                if (OnUpdate != null)
                {
                    OnUpdate();
                }
            }
            return(true);
        }
        bool UpdateChanges()
        {
            // Get the current status of the build
            int             MaxChanges;
            int             OldestChangeNumber = -1;
            int             NewestChangeNumber = -1;
            HashSet <int>   CurrentChangelists;
            SortedSet <int> PrevPromotedChangelists;

            lock (this)
            {
                MaxChanges = PendingMaxChanges;
                if (Changes.Count > 0)
                {
                    NewestChangeNumber = Changes.First().Number;
                    OldestChangeNumber = Changes.Last().Number;
                }
                CurrentChangelists      = new HashSet <int>(Changes.Select(x => x.Number));
                PrevPromotedChangelists = new SortedSet <int>(PromotedChangeNumbers);
            }

            // Build a full list of all the paths to sync
            List <string> DepotPaths = new List <string>();

            if (SelectedClientFileName.EndsWith(".uprojectdirs", StringComparison.InvariantCultureIgnoreCase))
            {
                DepotPaths.Add(String.Format("{0}/...", BranchClientPath));
            }
            else
            {
                DepotPaths.Add(String.Format("{0}/*", BranchClientPath));
                DepotPaths.Add(String.Format("{0}/Engine/...", BranchClientPath));
                DepotPaths.Add(String.Format("{0}/...", PerforceUtils.GetClientOrDepotDirectoryName(SelectedClientFileName)));
            }

            // Read any new changes
            List <PerforceChangeSummary> NewChanges;

            if (MaxChanges > CurrentMaxChanges)
            {
                if (!Perforce.FindChanges(DepotPaths, MaxChanges, out NewChanges, LogWriter))
                {
                    return(false);
                }
            }
            else
            {
                if (!Perforce.FindChanges(DepotPaths.Select(DepotPath => String.Format("{0}@>{1}", DepotPath, NewestChangeNumber)), -1, out NewChanges, LogWriter))
                {
                    return(false);
                }
            }

            // Remove anything we already have
            NewChanges.RemoveAll(x => CurrentChangelists.Contains(x.Number));

            // Update the change ranges
            if (NewChanges.Count > 0)
            {
                OldestChangeNumber = Math.Max(OldestChangeNumber, NewChanges.Last().Number);
                NewestChangeNumber = Math.Min(NewestChangeNumber, NewChanges.First().Number);
            }

            // Insert them into the builds list
            if (NewChanges.Count > 0 || MaxChanges < CurrentMaxChanges)
            {
                lock (this)
                {
                    Changes.UnionWith(NewChanges);
                    while (Changes.Count > MaxChanges)
                    {
                        Changes.Remove(Changes.Last());
                    }
                    CurrentMaxChanges = MaxChanges;
                }
                if (OnUpdate != null)
                {
                    OnUpdate();
                }
            }
            return(true);
        }
Example #10
0
        public bool Run(out string ErrorMessage)
        {
            PerforceConnection Perforce = new PerforceConnection(null, null, null);

            // Get the Perforce server info
            PerforceInfoRecord PerforceInfo;

            if (!Perforce.Info(out PerforceInfo, Log))
            {
                ErrorMessage = String.Format("Couldn't get Perforce server info");
                return(false);
            }
            if (String.IsNullOrEmpty(PerforceInfo.UserName))
            {
                ErrorMessage = "Missing user name in call to p4 info";
                return(false);
            }
            if (String.IsNullOrEmpty(PerforceInfo.HostName))
            {
                ErrorMessage = "Missing host name in call to p4 info";
                return(false);
            }
            ServerTimeZone = PerforceInfo.ServerTimeZone;

            // Find all the clients on this machine
            Log.WriteLine("Enumerating clients on local machine...");
            List <PerforceClientRecord> Clients;

            if (!Perforce.FindClients(out Clients, Log))
            {
                ErrorMessage = String.Format("Couldn't find any clients for this host.");
                return(false);
            }

            // Find any clients which are valid. If this is not exactly one, we should fail.
            List <PerforceConnection> CandidateClients = new List <PerforceConnection>();

            foreach (PerforceClientRecord Client in Clients)
            {
                // Make sure the client is well formed
                if (!String.IsNullOrEmpty(Client.Name) && (!String.IsNullOrEmpty(Client.Host) || !String.IsNullOrEmpty(Client.Owner)) && !String.IsNullOrEmpty(Client.Root))
                {
                    // Require either a username or host name match
                    if ((String.IsNullOrEmpty(Client.Host) || String.Compare(Client.Host, PerforceInfo.HostName, StringComparison.InvariantCultureIgnoreCase) == 0) && (String.IsNullOrEmpty(Client.Owner) || String.Compare(Client.Owner, PerforceInfo.UserName, StringComparison.InvariantCultureIgnoreCase) == 0))
                    {
                        if (!Utility.SafeIsFileUnderDirectory(NewSelectedFileName, Client.Root))
                        {
                            Log.WriteLine("Rejecting {0} due to root mismatch ({1})", Client.Name, Client.Root);
                            continue;
                        }

                        PerforceConnection CandidateClient = new PerforceConnection(PerforceInfo.UserName, Client.Name, Perforce.ServerAndPort);

                        bool bFileExists;
                        if (!CandidateClient.FileExists(NewSelectedFileName, out bFileExists, Log) || !bFileExists)
                        {
                            Log.WriteLine("Rejecting {0} due to file not existing in workspace", Client.Name);
                            continue;
                        }

                        List <PerforceFileRecord> Records;
                        if (!CandidateClient.Stat(NewSelectedFileName, out Records, Log))
                        {
                            Log.WriteLine("Rejecting {0} due to {1} not in depot", Client.Name, NewSelectedFileName);
                            continue;
                        }

                        Records.RemoveAll(x => !x.IsMapped);
                        if (Records.Count != 1)
                        {
                            Log.WriteLine("Rejecting {0} due to {1} matching records", Client.Name, Records.Count);
                            continue;
                        }

                        Log.WriteLine("Found valid client {0}", Client.Name);
                        CandidateClients.Add(CandidateClient);
                    }
                }
            }

            // Check there's only one client
            if (CandidateClients.Count == 0)
            {
                ErrorMessage = String.Format("Couldn't find any Perforce workspace containing {0}.", NewSelectedFileName);
                return(false);
            }
            else if (CandidateClients.Count > 1)
            {
                ErrorMessage = String.Format("Found multiple workspaces containing {0}:\n\n{1}\n\nCannot determine which to use.", Path.GetFileName(NewSelectedFileName), String.Join("\n", CandidateClients.Select(x => x.ClientName)));
                return(false);
            }

            // Take the client we've chosen
            PerforceClient = CandidateClients[0];

            // Get the client path for the project file
            if (!PerforceClient.ConvertToClientPath(NewSelectedFileName, out NewSelectedClientFileName, Log))
            {
                ErrorMessage = String.Format("Couldn't get client path for {0}", NewSelectedFileName);
                return(false);
            }

            // Figure out where the engine is in relation to it
            for (int EndIdx = NewSelectedClientFileName.Length - 1;; EndIdx--)
            {
                if (EndIdx < 2)
                {
                    ErrorMessage = String.Format("Could not find engine in Perforce relative to project path ({0})", NewSelectedClientFileName);
                    return(false);
                }
                if (NewSelectedClientFileName[EndIdx] == '/')
                {
                    bool bFileExists;
                    if (!PerforceClient.FileExists(NewSelectedClientFileName.Substring(0, EndIdx) + "/Engine/Source/UE4Editor.target.cs", out bFileExists, Log))
                    {
                        ErrorMessage = String.Format("Could not find engine in Perforce relative to project path ({0})", NewSelectedClientFileName);
                        return(false);
                    }
                    else if (bFileExists)
                    {
                        BranchClientPath = NewSelectedClientFileName.Substring(0, EndIdx);
                        break;
                    }
                }
            }
            Log.WriteLine("Found branch root at {0}", BranchClientPath);

            // Get the local branch root
            if (!PerforceClient.ConvertToLocalPath(BranchClientPath + "/Engine/Source/UE4Editor.target.cs", out BaseEditorTargetPath, Log))
            {
                ErrorMessage = String.Format("Couldn't get local path for editor target file");
                return(false);
            }

            // Find the editor target for this project
            if (NewSelectedFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
            {
                List <PerforceFileRecord> Files;
                if (PerforceClient.FindFiles(PerforceUtils.GetClientOrDepotDirectoryName(NewSelectedClientFileName) + "/Source/*Editor.Target.cs", out Files, Log) && Files.Count >= 1)
                {
                    PerforceFileRecord File = Files.FirstOrDefault(x => x.Action == null || !x.Action.Contains("delete"));
                    if (File == null)
                    {
                        Log.WriteLine("Couldn't find any non-deleted editor targets for this project.");
                    }
                    else
                    {
                        string DepotPath = File.DepotPath;
                        NewProjectEditorTarget = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(DepotPath.Substring(DepotPath.LastIndexOf('/') + 1)));
                        Log.WriteLine("Using {0} as editor target name (from {1})", NewProjectEditorTarget, Files[0]);
                    }
                }
                else
                {
                    Log.WriteLine("Couldn't find any editor targets for this project.");
                }
            }

            // Get a unique name for the project that's selected. For regular branches, this can be the depot path. For streams, we want to include the stream name to encode imports.
            if (PerforceClient.GetActiveStream(out StreamName, Log))
            {
                string ExpectedPrefix = String.Format("//{0}/", PerforceClient.ClientName);
                if (!NewSelectedClientFileName.StartsWith(ExpectedPrefix, StringComparison.InvariantCultureIgnoreCase))
                {
                    ErrorMessage = String.Format("Unexpected client path; expected '{0}' to begin with '{1}'", NewSelectedClientFileName, ExpectedPrefix);
                    return(false);
                }
                string StreamPrefix;
                if (!TryGetStreamPrefix(PerforceClient, StreamName, Log, out StreamPrefix))
                {
                    ErrorMessage = String.Format("Failed to get stream info for {0}", StreamName);
                    return(false);
                }
                NewSelectedProjectIdentifier = String.Format("{0}/{1}", StreamPrefix, NewSelectedClientFileName.Substring(ExpectedPrefix.Length));
            }
            else
            {
                if (!PerforceClient.ConvertToDepotPath(NewSelectedClientFileName, out NewSelectedProjectIdentifier, Log))
                {
                    ErrorMessage = String.Format("Couldn't get depot path for {0}", NewSelectedFileName);
                    return(false);
                }
            }

            // Read the project logo
            if (NewSelectedFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
            {
                string LogoFileName = Path.Combine(Path.GetDirectoryName(NewSelectedFileName), "Build", "UnrealGameSync.png");
                if (File.Exists(LogoFileName))
                {
                    try
                    {
                        // Duplicate the image, otherwise we'll leave the file locked
                        using (Image Image = Image.FromFile(LogoFileName))
                        {
                            ProjectLogo = new Bitmap(Image);
                        }
                    }
                    catch
                    {
                        ProjectLogo = null;
                    }
                }
            }

            // Succeeed!
            ErrorMessage = null;
            return(true);
        }
Example #11
0
        WorkspaceUpdateResult UpdateWorkspaceInternal(WorkspaceUpdateContext Context, out string StatusMessage)
        {
            string CmdExe = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe");

            if (!File.Exists(CmdExe))
            {
                StatusMessage = String.Format("Missing {0}.", CmdExe);
                return(WorkspaceUpdateResult.FailedToSync);
            }

            List <Tuple <string, TimeSpan> > Times = new List <Tuple <string, TimeSpan> >();

            int NumFilesSynced = 0;

            if (Context.Options.HasFlag(WorkspaceUpdateOptions.Sync) || Context.Options.HasFlag(WorkspaceUpdateOptions.SyncSingleChange))
            {
                using (TelemetryStopwatch Stopwatch = new TelemetryStopwatch("Sync", TelemetryProjectPath))
                {
                    Log.WriteLine("Syncing to {0}...", PendingChangeNumber);

                    // Find all the files that are out of date
                    Progress.Set("Finding files to sync...");

                    // Get the user's sync filter
                    FileFilter UserFilter = null;
                    if (Context.SyncFilter != null)
                    {
                        UserFilter = new FileFilter(FileFilterType.Include);
                        UserFilter.AddRules(Context.SyncFilter.Select(x => x.Trim()).Where(x => x.Length > 0 && !x.StartsWith(";") && !x.StartsWith("#")));
                    }

                    // Find all the server changes, and anything that's opened for edit locally. We need to sync files we have open to schedule a resolve.
                    List <string> SyncFiles = new List <string>();
                    foreach (string SyncPath in SyncPaths)
                    {
                        List <PerforceFileRecord> SyncRecords;
                        if (!Perforce.SyncPreview(SyncPath, PendingChangeNumber, !Context.Options.HasFlag(WorkspaceUpdateOptions.Sync), out SyncRecords, Log))
                        {
                            StatusMessage = String.Format("Couldn't enumerate changes matching {0}.", SyncPath);
                            return(WorkspaceUpdateResult.FailedToSync);
                        }

                        if (UserFilter != null)
                        {
                            SyncRecords.RemoveAll(x => !String.IsNullOrEmpty(x.ClientPath) && !MatchFilter(x.ClientPath, UserFilter));
                        }

                        SyncFiles.AddRange(SyncRecords.Select(x => x.DepotPath));

                        List <PerforceFileRecord> OpenRecords;
                        if (!Perforce.GetOpenFiles(SyncPath, out OpenRecords, Log))
                        {
                            StatusMessage = String.Format("Couldn't find open files matching {0}.", SyncPath);
                            return(WorkspaceUpdateResult.FailedToSync);
                        }

                        // don't force a sync on added files
                        SyncFiles.AddRange(OpenRecords.Where(x => x.Action != "add" && x.Action != "branch" && x.Action != "move/add").Select(x => x.DepotPath));
                    }

                    // Filter out all the binaries that we don't want
                    FileFilter Filter = new FileFilter(FileFilterType.Include);
                    Filter.Exclude("..." + BuildVersionFileName);
                    Filter.Exclude("..." + VersionHeaderFileName);
                    Filter.Exclude("..." + ObjectVersionFileName);
                    if (Context.Options.HasFlag(WorkspaceUpdateOptions.ContentOnly))
                    {
                        Filter.Exclude("*.usf");
                    }
                    SyncFiles.RemoveAll(x => !Filter.Matches(x));

                    // Sync them all
                    List <string>    TamperedFiles  = new List <string>();
                    HashSet <string> RemainingFiles = new HashSet <string>(SyncFiles, StringComparer.InvariantCultureIgnoreCase);
                    if (!Perforce.Sync(SyncFiles, PendingChangeNumber, Record => UpdateSyncProgress(Record, RemainingFiles, SyncFiles.Count), TamperedFiles, Log))
                    {
                        StatusMessage = "Aborted sync due to errors.";
                        return(WorkspaceUpdateResult.FailedToSync);
                    }

                    // If any files need to be clobbered, defer to the main thread to figure out which ones
                    if (TamperedFiles.Count > 0)
                    {
                        int NumNewFilesToClobber = 0;
                        foreach (string TamperedFile in TamperedFiles)
                        {
                            if (!Context.ClobberFiles.ContainsKey(TamperedFile))
                            {
                                Context.ClobberFiles[TamperedFile] = true;
                                NumNewFilesToClobber++;
                            }
                        }
                        if (NumNewFilesToClobber > 0)
                        {
                            StatusMessage = String.Format("Cancelled sync after checking files to clobber ({0} new files).", NumNewFilesToClobber);
                            return(WorkspaceUpdateResult.FilesToClobber);
                        }
                        foreach (string TamperedFile in TamperedFiles)
                        {
                            if (Context.ClobberFiles[TamperedFile] && !Perforce.ForceSync(TamperedFile, PendingChangeNumber, Log))
                            {
                                StatusMessage = String.Format("Couldn't sync {0}.", TamperedFile);
                                return(WorkspaceUpdateResult.FailedToSync);
                            }
                        }
                    }

                    if (Context.Options.HasFlag(WorkspaceUpdateOptions.Sync))
                    {
                        // Read the new config file
                        ProjectConfigFile = ReadProjectConfigFile(LocalRootPath, SelectedLocalFileName, Log);

                        // Get the branch name
                        string BranchOrStreamName;
                        if (!Perforce.GetActiveStream(out BranchOrStreamName, Log))
                        {
                            string DepotFileName;
                            if (!Perforce.ConvertToDepotPath(ClientRootPath + "/GenerateProjectFiles.bat", out DepotFileName, Log))
                            {
                                StatusMessage = String.Format("Couldn't determine branch name for {0}.", SelectedClientFileName);
                                return(WorkspaceUpdateResult.FailedToSync);
                            }
                            BranchOrStreamName = PerforceUtils.GetClientOrDepotDirectoryName(DepotFileName);
                        }

                        // Find the last code change before this changelist. For consistency in versioning between local builds and precompiled binaries, we need to use the last submitted code changelist as our version number.
                        List <PerforceChangeSummary> CodeChanges;
                        if (!Perforce.FindChanges(new string[] { ".cs", ".h", ".cpp", ".usf" }.SelectMany(x => SyncPaths.Select(y => String.Format("{0}{1}@<={2}", y, x, PendingChangeNumber))), 1, out CodeChanges, Log))
                        {
                            StatusMessage = String.Format("Couldn't determine last code changelist before CL {0}.", PendingChangeNumber);
                            return(WorkspaceUpdateResult.FailedToSync);
                        }
                        if (CodeChanges.Count == 0)
                        {
                            StatusMessage = String.Format("Could not find any code changes before CL {0}.", PendingChangeNumber);
                            return(WorkspaceUpdateResult.FailedToSync);
                        }

                        // Get the last code change
                        int VersionChangeNumber;
                        if (ProjectConfigFile.GetValue("Options.VersionToLastCodeChange", true))
                        {
                            VersionChangeNumber = CodeChanges.Max(x => x.Number);
                        }
                        else
                        {
                            VersionChangeNumber = PendingChangeNumber;
                        }

                        // Update the version files
                        if (ProjectConfigFile.GetValue("Options.UseFastModularVersioning", false))
                        {
                            Dictionary <string, string> BuildVersionStrings = new Dictionary <string, string>();
                            BuildVersionStrings["\"Changelist\":"] = String.Format(" {0},", VersionChangeNumber);
                            BuildVersionStrings["\"BranchName\":"] = String.Format(" \"{0}\"", BranchOrStreamName.Replace('/', '+'));
                            if (!UpdateVersionFile(ClientRootPath + BuildVersionFileName, BuildVersionStrings, PendingChangeNumber))
                            {
                                StatusMessage = String.Format("Failed to update {0}.", BuildVersionFileName);
                                return(WorkspaceUpdateResult.FailedToSync);
                            }

                            Dictionary <string, string> VersionHeaderStrings = new Dictionary <string, string>();
                            VersionHeaderStrings["#define ENGINE_IS_PROMOTED_BUILD"] = " (0)";
                            VersionHeaderStrings["#define BUILT_FROM_CHANGELIST"]    = " 0";
                            VersionHeaderStrings["#define BRANCH_NAME"] = " \"" + BranchOrStreamName.Replace('/', '+') + "\"";
                            if (!UpdateVersionFile(ClientRootPath + VersionHeaderFileName, VersionHeaderStrings, PendingChangeNumber))
                            {
                                StatusMessage = String.Format("Failed to update {0}.", VersionHeaderFileName);
                                return(WorkspaceUpdateResult.FailedToSync);
                            }
                            if (!UpdateVersionFile(ClientRootPath + ObjectVersionFileName, new Dictionary <string, string>(), PendingChangeNumber))
                            {
                                StatusMessage = String.Format("Failed to update {0}.", ObjectVersionFileName);
                                return(WorkspaceUpdateResult.FailedToSync);
                            }
                        }
                        else
                        {
                            if (!UpdateVersionFile(ClientRootPath + BuildVersionFileName, new Dictionary <string, string>(), PendingChangeNumber))
                            {
                                StatusMessage = String.Format("Failed to update {0}", BuildVersionFileName);
                                return(WorkspaceUpdateResult.FailedToSync);
                            }

                            Dictionary <string, string> VersionStrings = new Dictionary <string, string>();
                            VersionStrings["#define ENGINE_VERSION"]           = " " + VersionChangeNumber.ToString();
                            VersionStrings["#define ENGINE_IS_PROMOTED_BUILD"] = " (0)";
                            VersionStrings["#define BUILT_FROM_CHANGELIST"]    = " " + VersionChangeNumber.ToString();
                            VersionStrings["#define BRANCH_NAME"] = " \"" + BranchOrStreamName.Replace('/', '+') + "\"";
                            if (!UpdateVersionFile(ClientRootPath + VersionHeaderFileName, VersionStrings, PendingChangeNumber))
                            {
                                StatusMessage = String.Format("Failed to update {0}", VersionHeaderFileName);
                                return(WorkspaceUpdateResult.FailedToSync);
                            }
                            if (!UpdateVersionFile(ClientRootPath + ObjectVersionFileName, VersionStrings, PendingChangeNumber))
                            {
                                StatusMessage = String.Format("Failed to update {0}", ObjectVersionFileName);
                                return(WorkspaceUpdateResult.FailedToSync);
                            }
                        }

                        // Remove all the receipts for build targets in this branch
                        if (SelectedClientFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
                        {
                            Perforce.Sync(PerforceUtils.GetClientOrDepotDirectoryName(SelectedClientFileName) + "/Build/Receipts/...#0", Log);
                        }
                    }

                    // Check if there are any files which need resolving
                    List <PerforceFileRecord> UnresolvedFiles;
                    if (!FindUnresolvedFiles(SyncPaths, out UnresolvedFiles))
                    {
                        StatusMessage = "Couldn't get list of unresolved files.";
                        return(WorkspaceUpdateResult.FailedToSync);
                    }
                    if (UnresolvedFiles.Count > 0 && Context.Options.HasFlag(WorkspaceUpdateOptions.AutoResolveChanges))
                    {
                        foreach (PerforceFileRecord UnresolvedFile in UnresolvedFiles)
                        {
                            Perforce.AutoResolveFile(UnresolvedFile.DepotPath, Log);
                        }
                        if (!FindUnresolvedFiles(SyncPaths, out UnresolvedFiles))
                        {
                            StatusMessage = "Couldn't get list of unresolved files.";
                            return(WorkspaceUpdateResult.FailedToSync);
                        }
                    }
                    if (UnresolvedFiles.Count > 0)
                    {
                        Log.WriteLine("{0} files need resolving:", UnresolvedFiles.Count);
                        foreach (PerforceFileRecord UnresolvedFile in UnresolvedFiles)
                        {
                            Log.WriteLine("  {0}", UnresolvedFile.ClientPath);
                        }
                        StatusMessage = "Files need resolving.";
                        return(WorkspaceUpdateResult.FilesToResolve);
                    }

                    // Update the current change number. Everything else happens for the new change.
                    if (Context.Options.HasFlag(WorkspaceUpdateOptions.Sync))
                    {
                        CurrentChangeNumber = PendingChangeNumber;
                    }

                    // Update the timing info
                    Times.Add(new Tuple <string, TimeSpan>("Sync", Stopwatch.Stop("Success")));

                    // Save the number of files synced
                    NumFilesSynced = SyncFiles.Count;
                    Log.WriteLine();
                }
            }

            // Extract an archive from the depot path
            if (Context.Options.HasFlag(WorkspaceUpdateOptions.SyncArchives))
            {
                using (TelemetryStopwatch Stopwatch = new TelemetryStopwatch("Archives", TelemetryProjectPath))
                {
                    // Create the directory for extracted archive manifests
                    string ManifestDirectoryName;
                    if (SelectedLocalFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
                    {
                        ManifestDirectoryName = Path.Combine(Path.GetDirectoryName(SelectedLocalFileName), "Saved", "UnrealGameSync");
                    }
                    else
                    {
                        ManifestDirectoryName = Path.Combine(Path.GetDirectoryName(SelectedLocalFileName), "Engine", "Saved", "UnrealGameSync");
                    }
                    Directory.CreateDirectory(ManifestDirectoryName);

                    // Sync and extract (or just remove) the given archives
                    foreach (KeyValuePair <string, string> ArchiveTypeAndDepotPath in Context.ArchiveTypeToDepotPath)
                    {
                        // Remove any existing binaries
                        string ManifestFileName = Path.Combine(ManifestDirectoryName, String.Format("{0}.zipmanifest", ArchiveTypeAndDepotPath.Key));
                        if (File.Exists(ManifestFileName))
                        {
                            Log.WriteLine("Removing {0} binaries...", ArchiveTypeAndDepotPath.Key);
                            Progress.Set(String.Format("Removing {0} binaries...", ArchiveTypeAndDepotPath.Key), 0.0f);
                            ArchiveUtils.RemoveExtractedFiles(LocalRootPath, ManifestFileName, Progress, Log);
                            Log.WriteLine();
                        }

                        // If we have a new depot path, sync it down and extract it
                        if (ArchiveTypeAndDepotPath.Value != null)
                        {
                            string TempZipFileName = Path.GetTempFileName();
                            try
                            {
                                Log.WriteLine("Syncing {0} binaries...", ArchiveTypeAndDepotPath.Key.ToLowerInvariant());
                                Progress.Set(String.Format("Syncing {0} binaries...", ArchiveTypeAndDepotPath.Key.ToLowerInvariant()), 0.0f);
                                if (!Perforce.PrintToFile(ArchiveTypeAndDepotPath.Value, TempZipFileName, Log) || new FileInfo(TempZipFileName).Length == 0)
                                {
                                    StatusMessage = String.Format("Couldn't read {0}", ArchiveTypeAndDepotPath.Value);
                                    return(WorkspaceUpdateResult.FailedToSync);
                                }
                                ArchiveUtils.ExtractFiles(TempZipFileName, LocalRootPath, ManifestFileName, Progress, Log);
                                Log.WriteLine();
                            }
                            finally
                            {
                                File.SetAttributes(TempZipFileName, FileAttributes.Normal);
                                File.Delete(TempZipFileName);
                            }
                        }
                    }

                    // Add the finish time
                    Times.Add(new Tuple <string, TimeSpan>("Archive", Stopwatch.Stop("Success")));
                }
            }

            // Generate project files in the workspace
            if (Context.Options.HasFlag(WorkspaceUpdateOptions.GenerateProjectFiles))
            {
                using (TelemetryStopwatch Stopwatch = new TelemetryStopwatch("Prj gen", TelemetryProjectPath))
                {
                    Progress.Set("Generating project files...", 0.0f);

                    string ProjectFileArgument = "";
                    if (SelectedLocalFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
                    {
                        ProjectFileArgument = String.Format("\"{0}\" ", SelectedLocalFileName);
                    }

                    string CommandLine = String.Format("/C \"\"{0}\" {1}-progress\"", Path.Combine(LocalRootPath, "GenerateProjectFiles.bat"), ProjectFileArgument);

                    Log.WriteLine("Generating project files...");
                    Log.WriteLine("gpf> Running {0} {1}", CmdExe, CommandLine);

                    int GenerateProjectFilesResult = Utility.ExecuteProcess(CmdExe, CommandLine, null, new ProgressTextWriter(Progress, new PrefixedTextWriter("gpf> ", Log)));
                    if (GenerateProjectFilesResult != 0)
                    {
                        StatusMessage = String.Format("Failed to generate project files (exit code {0}).", GenerateProjectFilesResult);
                        return(WorkspaceUpdateResult.FailedToCompile);
                    }

                    Log.WriteLine();
                    Times.Add(new Tuple <string, TimeSpan>("Prj gen", Stopwatch.Stop("Success")));
                }
            }

            // Build everything using MegaXGE
            if (Context.Options.HasFlag(WorkspaceUpdateOptions.Build))
            {
                // Compile all the build steps together
                Dictionary <Guid, ConfigObject> BuildStepObjects = Context.DefaultBuildSteps.ToDictionary(x => x.Key, x => new ConfigObject(x.Value));
                BuildStep.MergeBuildStepObjects(BuildStepObjects, ProjectConfigFile.GetValues("Build.Step", new string[0]).Select(x => new ConfigObject(x)));
                BuildStep.MergeBuildStepObjects(BuildStepObjects, Context.UserBuildStepObjects);

                // Construct build steps from them
                List <BuildStep> BuildSteps = BuildStepObjects.Values.Select(x => new BuildStep(x)).OrderBy(x => x.OrderIndex).ToList();
                if (Context.CustomBuildSteps != null && Context.CustomBuildSteps.Count > 0)
                {
                    BuildSteps.RemoveAll(x => !Context.CustomBuildSteps.Contains(x.UniqueId));
                }
                else if (Context.Options.HasFlag(WorkspaceUpdateOptions.ScheduledBuild))
                {
                    BuildSteps.RemoveAll(x => !x.bScheduledSync);
                }
                else
                {
                    BuildSteps.RemoveAll(x => !x.bNormalSync);
                }

                // Check if the last successful build was before a change that we need to force a clean for
                bool bForceClean = false;
                if (LastBuiltChangeNumber != 0)
                {
                    foreach (string CleanBuildChange in ProjectConfigFile.GetValues("ForceClean.Changelist", new string[0]))
                    {
                        int ChangeNumber;
                        if (int.TryParse(CleanBuildChange, out ChangeNumber))
                        {
                            if ((LastBuiltChangeNumber >= ChangeNumber) != (CurrentChangeNumber >= ChangeNumber))
                            {
                                Log.WriteLine("Forcing clean build due to changelist {0}.", ChangeNumber);
                                Log.WriteLine();
                                bForceClean = true;
                                break;
                            }
                        }
                    }
                }

                // Execute them all
                string TelemetryEventName = (Context.UserBuildStepObjects.Count > 0)? "CustomBuild" : Context.Options.HasFlag(WorkspaceUpdateOptions.UseIncrementalBuilds) ? "Compile" : "FullCompile";
                using (TelemetryStopwatch Stopwatch = new TelemetryStopwatch(TelemetryEventName, TelemetryProjectPath))
                {
                    Progress.Set("Starting build...", 0.0f);

                    // Check we've built UBT (it should have been compiled by generating project files)
                    string UnrealBuildToolPath = Path.Combine(LocalRootPath, "Engine", "Binaries", "DotNET", "UnrealBuildTool.exe");
                    if (!File.Exists(UnrealBuildToolPath))
                    {
                        StatusMessage = String.Format("Couldn't find {0}", UnrealBuildToolPath);
                        return(WorkspaceUpdateResult.FailedToCompile);
                    }

                    // Execute all the steps
                    float MaxProgressFraction = 0.0f;
                    foreach (BuildStep Step in BuildSteps)
                    {
                        MaxProgressFraction += (float)Step.EstimatedDuration / (float)Math.Max(BuildSteps.Sum(x => x.EstimatedDuration), 1);

                        Progress.Set(Step.StatusText);
                        Progress.Push(MaxProgressFraction);

                        Log.WriteLine(Step.StatusText);

                        switch (Step.Type)
                        {
                        case BuildStepType.Compile:
                            using (TelemetryStopwatch StepStopwatch = new TelemetryStopwatch("Compile:" + Step.Target, TelemetryProjectPath))
                            {
                                string CommandLine = String.Format("{0} {1} {2} {3} -NoHotReloadFromIDE", Step.Target, Step.Platform, Step.Configuration, Utility.ExpandVariables(Step.Arguments, Context.Variables));
                                if (!Context.Options.HasFlag(WorkspaceUpdateOptions.UseIncrementalBuilds) || bForceClean)
                                {
                                    Log.WriteLine("ubt> Running {0} {1} -clean", UnrealBuildToolPath, CommandLine);
                                    Utility.ExecuteProcess(UnrealBuildToolPath, CommandLine + " -clean", null, new ProgressTextWriter(Progress, new PrefixedTextWriter("ubt> ", Log)));
                                }

                                Log.WriteLine("ubt> Running {0} {1} -progress", UnrealBuildToolPath, CommandLine);

                                int ResultFromBuild = Utility.ExecuteProcess(UnrealBuildToolPath, CommandLine + " -progress", null, new ProgressTextWriter(Progress, new PrefixedTextWriter("ubt> ", Log)));
                                if (ResultFromBuild != 0)
                                {
                                    StatusMessage = String.Format("Failed to compile {0}.", Step.Target);
                                    return((HasModifiedSourceFiles() || Context.UserBuildStepObjects.Count > 0)? WorkspaceUpdateResult.FailedToCompile : WorkspaceUpdateResult.FailedToCompileWithCleanWorkspace);
                                }
                            }
                            break;

                        case BuildStepType.Cook:
                            using (TelemetryStopwatch StepStopwatch = new TelemetryStopwatch("Cook/Launch: " + Path.GetFileNameWithoutExtension(Step.FileName), TelemetryProjectPath))
                            {
                                string LocalRunUAT = Path.Combine(LocalRootPath, "Engine", "Build", "BatchFiles", "RunUAT.bat");
                                string Arguments   = String.Format("/C \"\"{0}\" -profile=\"{1}\"\"", LocalRunUAT, Path.Combine(LocalRootPath, Step.FileName));
                                Log.WriteLine("uat> Running {0} {1}", LocalRunUAT, Arguments);

                                int ResultFromUAT = Utility.ExecuteProcess(CmdExe, Arguments, null, new ProgressTextWriter(Progress, new PrefixedTextWriter("uat> ", Log)));
                                if (ResultFromUAT != 0)
                                {
                                    StatusMessage = String.Format("Cook failed. ({0})", ResultFromUAT);
                                    return(WorkspaceUpdateResult.FailedToCompile);
                                }
                            }
                            break;

                        case BuildStepType.Other:
                            using (TelemetryStopwatch StepStopwatch = new TelemetryStopwatch("Custom: " + Path.GetFileNameWithoutExtension(Step.FileName), TelemetryProjectPath))
                            {
                                string ToolFileName  = Path.Combine(LocalRootPath, Utility.ExpandVariables(Step.FileName, Context.Variables));
                                string ToolArguments = Utility.ExpandVariables(Step.Arguments, Context.Variables);
                                Log.WriteLine("tool> Running {0} {1}", ToolFileName, ToolArguments);

                                if (Step.bUseLogWindow)
                                {
                                    int ResultFromTool = Utility.ExecuteProcess(ToolFileName, ToolArguments, null, new ProgressTextWriter(Progress, new PrefixedTextWriter("tool> ", Log)));
                                    if (ResultFromTool != 0)
                                    {
                                        StatusMessage = String.Format("Tool terminated with exit code {0}.", ResultFromTool);
                                        return(WorkspaceUpdateResult.FailedToCompile);
                                    }
                                }
                                else
                                {
                                    using (Process.Start(ToolFileName, ToolArguments))
                                    {
                                    }
                                }
                            }
                            break;
                        }

                        Log.WriteLine();
                        Progress.Pop();
                    }

                    Times.Add(new Tuple <string, TimeSpan>("Build", Stopwatch.Stop("Success")));
                }

                // Update the last successful build change number
                if (Context.CustomBuildSteps == null || Context.CustomBuildSteps.Count == 0)
                {
                    LastBuiltChangeNumber = CurrentChangeNumber;
                }
            }

            // Write out all the timing information
            Log.WriteLine("Total time : " + FormatTime(Times.Sum(x => (long)(x.Item2.TotalMilliseconds / 1000))));
            foreach (Tuple <string, TimeSpan> Time in Times)
            {
                Log.WriteLine("   {0,-8}: {1}", Time.Item1, FormatTime((long)(Time.Item2.TotalMilliseconds / 1000)));
            }
            if (NumFilesSynced > 0)
            {
                Log.WriteLine("{0} files synced.", NumFilesSynced);
            }

            Log.WriteLine();
            Log.WriteLine("UPDATE SUCCEEDED");

            StatusMessage = "Update succeeded";
            return(WorkspaceUpdateResult.Success);
        }
        private SelectStreamWindow(List <PerforceStreamRecord> Streams, string StreamName)
        {
            InitializeComponent();

            this.SelectedStream = StreamName;

            // Set up the image list
            ImageList PerforceImageList = new ImageList();

            PerforceImageList.ImageSize  = new Size(16, 16);
            PerforceImageList.ColorDepth = ColorDepth.Depth32Bit;
            PerforceImageList.Images.AddStrip(Properties.Resources.Perforce);
            StreamsTreeView.ImageList = PerforceImageList;

            // Build a map of stream names to their nodes
            Dictionary <string, StreamNode> IdentifierToNode = new Dictionary <string, StreamNode>(StringComparer.InvariantCultureIgnoreCase);

            foreach (PerforceStreamRecord Stream in Streams)
            {
                if (Stream.Identifier != null && Stream.Name != null)
                {
                    IdentifierToNode[Stream.Identifier] = new StreamNode(Stream);
                }
            }

            // Create all the depots
            Dictionary <string, StreamDepot> NameToDepot = new Dictionary <string, StreamDepot>(StringComparer.InvariantCultureIgnoreCase);

            foreach (StreamNode Node in IdentifierToNode.Values)
            {
                if (Node.Record.Parent == null)
                {
                    string DepotName;
                    if (PerforceUtils.TryGetDepotName(Node.Record.Identifier, out DepotName))
                    {
                        StreamDepot Depot;
                        if (!NameToDepot.TryGetValue(DepotName, out Depot))
                        {
                            Depot = new StreamDepot(DepotName);
                            NameToDepot.Add(DepotName, Depot);
                        }
                        Depot.RootNodes.Add(Node);
                    }
                }
                else
                {
                    StreamNode ParentNode;
                    if (IdentifierToNode.TryGetValue(Node.Record.Parent, out ParentNode))
                    {
                        ParentNode.ChildNodes.Add(Node);
                    }
                }
            }

            // Sort the tree
            Depots = NameToDepot.Values.OrderBy(x => x.Name).ToList();
            foreach (StreamDepot Depot in Depots)
            {
                Depot.Sort();
            }

            // Update the contents of the tree
            PopulateTree();
            UpdateOkButton();
        }
        public bool Run(PerforceConnection Perforce, TextWriter Log, out string ErrorMessage)
        {
            // Get the perforce server settings
            PerforceInfoRecord PerforceInfo;

            if (!Perforce.Info(out PerforceInfo, Log))
            {
                ErrorMessage = String.Format("Couldn't get Perforce server info");
                return(false);
            }

            // Configure the time zone
            ServerTimeZone = PerforceInfo.ServerTimeZone;

            // If we're using the legacy path of specifying a file, figure out the workspace name now
            if (SelectedProject.Type == UserSelectedProjectType.Client)
            {
                // Get the client path
                NewSelectedClientFileName = SelectedProject.ClientPath;

                // Get the client name
                string ClientName;
                if (!PerforceUtils.TryGetClientName(NewSelectedClientFileName, out ClientName))
                {
                    ErrorMessage = String.Format("Couldn't get client name from {0}", NewSelectedClientFileName);
                    return(false);
                }

                // Create the client
                PerforceClient = new PerforceConnection(Perforce.UserName, ClientName, Perforce.ServerAndPort);

                // Figure out the path on the client
                if (!PerforceClient.ConvertToLocalPath(NewSelectedClientFileName, out NewSelectedFileName, Log))
                {
                    ErrorMessage = String.Format("Couldn't get client path for {0}", NewSelectedFileName);
                    return(false);
                }
            }
            else if (SelectedProject.Type == UserSelectedProjectType.Local)
            {
                // Use the path as the selected filename
                NewSelectedFileName = SelectedProject.LocalPath;

                // Make sure the project exists
                if (!File.Exists(SelectedProject.LocalPath))
                {
                    ErrorMessage = String.Format("{0} does not exist.", SelectedProject.LocalPath);
                    return(false);
                }

                // Find all the clients on this machine
                Log.WriteLine("Enumerating clients on local machine...");
                List <PerforceClientRecord> Clients;
                if (!Perforce.FindClients(out Clients, Log))
                {
                    ErrorMessage = String.Format("Couldn't find any clients for this host.");
                    return(false);
                }

                // Find any clients which are valid. If this is not exactly one, we should fail.
                List <PerforceConnection> CandidateClients = new List <PerforceConnection>();
                foreach (PerforceClientRecord Client in Clients)
                {
                    // Make sure the client is well formed
                    if (!String.IsNullOrEmpty(Client.Name) && (!String.IsNullOrEmpty(Client.Host) || !String.IsNullOrEmpty(Client.Owner)) && !String.IsNullOrEmpty(Client.Root))
                    {
                        // Require either a username or host name match
                        if ((String.IsNullOrEmpty(Client.Host) || String.Compare(Client.Host, PerforceInfo.HostName, StringComparison.InvariantCultureIgnoreCase) == 0) && (String.IsNullOrEmpty(Client.Owner) || String.Compare(Client.Owner, PerforceInfo.UserName, StringComparison.InvariantCultureIgnoreCase) == 0))
                        {
                            if (!Utility.SafeIsFileUnderDirectory(NewSelectedFileName, Client.Root))
                            {
                                Log.WriteLine("Rejecting {0} due to root mismatch ({1})", Client.Name, Client.Root);
                                continue;
                            }

                            PerforceConnection CandidateClient = new PerforceConnection(PerforceInfo.UserName, Client.Name, Perforce.ServerAndPort);

                            bool bFileExists;
                            if (!CandidateClient.FileExists(NewSelectedFileName, out bFileExists, Log) || !bFileExists)
                            {
                                Log.WriteLine("Rejecting {0} due to file not existing in workspace", Client.Name);
                                continue;
                            }

                            List <PerforceFileRecord> Records;
                            if (!CandidateClient.Stat(NewSelectedFileName, out Records, Log))
                            {
                                Log.WriteLine("Rejecting {0} due to {1} not in depot", Client.Name, NewSelectedFileName);
                                continue;
                            }

                            Records.RemoveAll(x => !x.IsMapped);
                            if (Records.Count == 0)
                            {
                                Log.WriteLine("Rejecting {0} due to {1} matching records", Client.Name, Records.Count);
                                continue;
                            }

                            Log.WriteLine("Found valid client {0}", Client.Name);
                            CandidateClients.Add(CandidateClient);
                        }
                    }
                }

                // Check there's only one client
                if (CandidateClients.Count == 0)
                {
                    ErrorMessage = String.Format("Couldn't find any Perforce workspace containing {0}. Check your connection settings.", NewSelectedFileName);
                    return(false);
                }
                else if (CandidateClients.Count > 1)
                {
                    ErrorMessage = String.Format("Found multiple workspaces containing {0}:\n\n{1}\n\nCannot determine which to use.", Path.GetFileName(NewSelectedFileName), String.Join("\n", CandidateClients.Select(x => x.ClientName)));
                    return(false);
                }

                // Take the client we've chosen
                PerforceClient = CandidateClients[0];

                // Get the client path for the project file
                if (!PerforceClient.ConvertToClientPath(NewSelectedFileName, out NewSelectedClientFileName, Log))
                {
                    ErrorMessage = String.Format("Couldn't get client path for {0}", NewSelectedFileName);
                    return(false);
                }
            }
            else
            {
                throw new InvalidDataException("Invalid selected project type");
            }

            // Normalize the filename
            NewSelectedFileName = Path.GetFullPath(NewSelectedFileName).Replace('/', Path.DirectorySeparatorChar);

            // Make sure the path case is correct. This can cause UBT intermediates to be out of date if the case mismatches.
            NewSelectedFileName = Utility.GetPathWithCorrectCase(new FileInfo(NewSelectedFileName));

            // Update the selected project with all the data we've found
            SelectedProject = new UserSelectedProjectSettings(Perforce.ServerAndPort, Perforce.UserName, SelectedProject.Type, NewSelectedClientFileName, NewSelectedFileName);

            // Figure out where the engine is in relation to it
            for (int EndIdx = NewSelectedClientFileName.Length - 1;; EndIdx--)
            {
                if (EndIdx < 2)
                {
                    ErrorMessage = String.Format("Could not find engine in Perforce relative to project path ({0})", NewSelectedClientFileName);
                    return(false);
                }
                if (NewSelectedClientFileName[EndIdx] == '/')
                {
                    bool bFileExists;
                    if (PerforceClient.FileExists(NewSelectedClientFileName.Substring(0, EndIdx) + "/Engine/Build/Build.version", out bFileExists, Log) && bFileExists)
                    {
                        BranchClientPath = NewSelectedClientFileName.Substring(0, EndIdx);
                        break;
                    }
                }
            }
            Log.WriteLine("Found branch root at {0}", BranchClientPath);

            // Get the local branch root
            string BuildVersionPath;

            if (!PerforceClient.ConvertToLocalPath(BranchClientPath + "/Engine/Build/Build.version", out BuildVersionPath, Log))
            {
                ErrorMessage = String.Format("Couldn't get local path for Engine/Build/Build.version");
                return(false);
            }
            BranchDirectoryName = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(BuildVersionPath), "..", ".."));

            // Find the editor target for this project
            if (NewSelectedFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
            {
                List <PerforceFileRecord> Files;
                if (PerforceClient.FindFiles(PerforceUtils.GetClientOrDepotDirectoryName(NewSelectedClientFileName) + "/Source/*Editor.Target.cs", out Files, Log) && Files.Count >= 1)
                {
                    PerforceFileRecord File = Files.FirstOrDefault(x => x.Action == null || !x.Action.Contains("delete"));
                    if (File != null)
                    {
                        string DepotPath = File.DepotPath;
                        NewProjectEditorTarget = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(DepotPath.Substring(DepotPath.LastIndexOf('/') + 1)));
                        Log.WriteLine("Using {0} as editor target name (from {1})", NewProjectEditorTarget, Files[0]);
                    }
                }
                if (NewProjectEditorTarget == null)
                {
                    Log.WriteLine("Couldn't find any editor targets for this project.");
                }
            }

            // Get a unique name for the project that's selected. For regular branches, this can be the depot path. For streams, we want to include the stream name to encode imports.
            if (PerforceClient.GetActiveStream(out StreamName, Log))
            {
                string ExpectedPrefix = String.Format("//{0}/", PerforceClient.ClientName);
                if (!NewSelectedClientFileName.StartsWith(ExpectedPrefix, StringComparison.InvariantCultureIgnoreCase))
                {
                    ErrorMessage = String.Format("Unexpected client path; expected '{0}' to begin with '{1}'", NewSelectedClientFileName, ExpectedPrefix);
                    return(false);
                }
                string StreamPrefix;
                if (!TryGetStreamPrefix(PerforceClient, StreamName, Log, out StreamPrefix))
                {
                    ErrorMessage = String.Format("Failed to get stream info for {0}", StreamName);
                    return(false);
                }
                NewSelectedProjectIdentifier = String.Format("{0}/{1}", StreamPrefix, NewSelectedClientFileName.Substring(ExpectedPrefix.Length));
            }
            else
            {
                if (!PerforceClient.ConvertToDepotPath(NewSelectedClientFileName, out NewSelectedProjectIdentifier, Log))
                {
                    ErrorMessage = String.Format("Couldn't get depot path for {0}", NewSelectedFileName);
                    return(false);
                }
            }

            // Read the project logo
            if (NewSelectedFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase))
            {
                string LogoFileName = Path.Combine(Path.GetDirectoryName(NewSelectedFileName), "Build", "UnrealGameSync.png");
                if (File.Exists(LogoFileName))
                {
                    try
                    {
                        // Duplicate the image, otherwise we'll leave the file locked
                        using (Image Image = Image.FromFile(LogoFileName))
                        {
                            ProjectLogo = new Bitmap(Image);
                        }
                    }
                    catch
                    {
                        ProjectLogo = null;
                    }
                }
            }

            // Figure out if it's an enterprise project
            List <string> ProjectLines;

            if (NewSelectedClientFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase) && PerforceClient.Print(NewSelectedClientFileName, out ProjectLines, Log))
            {
                string Text = String.Join("\n", ProjectLines);
                bIsEnterpriseProject = Utility.IsEnterpriseProjectFromText(Text);
            }

            // Read the initial config file
            LocalConfigFiles        = new List <KeyValuePair <string, DateTime> >();
            LatestProjectConfigFile = PerforceMonitor.ReadProjectConfigFile(PerforceClient, BranchClientPath, NewSelectedClientFileName, LocalConfigFiles, Log);

            // Succeed!
            ErrorMessage = null;
            return(true);
        }