public static bool WriteWorkingManifest(string FileName, string TemporaryFileName, WorkingManifest Manifest) { if (!WriteXmlObject(TemporaryFileName, Manifest)) { return(false); } if (!SafeModifyFileAttributes(TemporaryFileName, FileAttributes.Hidden, 0)) { return(false); } if (!SafeDeleteFile(FileName)) { return(false); } if (!SafeMoveFile(TemporaryFileName, FileName)) { return(false); } return(true); }
static bool UpdateWorkingTree(bool bDryRun, string RootPath, HashSet <string> ExcludeFolders, int NumThreads, int MaxRetries, string ProxyUrl, OverwriteMode Overwrite) { // Start scanning on the working directory if (ExcludeFolders.Count > 0) { Log.WriteLine("Checking dependencies (excluding {0})...", String.Join(", ", ExcludeFolders)); } else { Log.WriteLine("Checking dependencies..."); } // Figure out the path to the working manifest string WorkingManifestPath = Path.Combine(RootPath, ".ue4dependencies"); // Recover from any interrupted transaction to the working manifest, by moving the temporary file into place. string TempWorkingManifestPath = WorkingManifestPath + TempManifestExtension; if (File.Exists(TempWorkingManifestPath) && !File.Exists(WorkingManifestPath) && !SafeMoveFile(TempWorkingManifestPath, WorkingManifestPath)) { return(false); } // Read the initial manifest, or create a new one WorkingManifest CurrentManifest; if (!File.Exists(WorkingManifestPath) || !ReadXmlObject(WorkingManifestPath, out CurrentManifest)) { CurrentManifest = new WorkingManifest(); } // Remove all the in-progress download files left over from previous runs foreach (WorkingFile InitialFile in CurrentManifest.Files) { if (InitialFile.Timestamp == 0) { string IncomingFilePath = Path.Combine(RootPath, InitialFile.Name + IncomingFileSuffix); if (File.Exists(IncomingFilePath) && !SafeDeleteFile(IncomingFilePath)) { return(false); } } } // Find all the manifests and push them into dictionaries Dictionary <string, DependencyFile> TargetFiles = new Dictionary <string, DependencyFile>(StringComparer.InvariantCultureIgnoreCase); Dictionary <string, DependencyBlob> TargetBlobs = new Dictionary <string, DependencyBlob>(StringComparer.InvariantCultureIgnoreCase); Dictionary <string, DependencyPack> TargetPacks = new Dictionary <string, DependencyPack>(StringComparer.InvariantCultureIgnoreCase); foreach (string BaseFolder in Directory.EnumerateDirectories(RootPath)) { string BuildFolder = Path.Combine(BaseFolder, "Build"); if (Directory.Exists(BuildFolder)) { foreach (string ManifestFileName in Directory.EnumerateFiles(BuildFolder, "*.gitdeps.xml")) { // Read this manifest DependencyManifest NewTargetManifest; if (!ReadXmlObject(ManifestFileName, out NewTargetManifest)) { return(false); } // Add all the files, blobs and packs into the shared dictionaries foreach (DependencyFile NewFile in NewTargetManifest.Files) { TargetFiles[NewFile.Name] = NewFile; } foreach (DependencyBlob NewBlob in NewTargetManifest.Blobs) { TargetBlobs[NewBlob.Hash] = NewBlob; } foreach (DependencyPack NewPack in NewTargetManifest.Packs) { TargetPacks[NewPack.Hash] = NewPack; } } } } // Find all the existing files in the working directory from previous runs. Use the working manifest to cache hashes for them based on timestamp, but recalculate them as needed. Dictionary <string, WorkingFile> CurrentFileLookup = new Dictionary <string, WorkingFile>(); foreach (WorkingFile CurrentFile in CurrentManifest.Files) { // Update the hash for this file string CurrentFilePath = Path.Combine(RootPath, CurrentFile.Name); if (File.Exists(CurrentFilePath)) { long LastWriteTime = File.GetLastWriteTimeUtc(CurrentFilePath).Ticks; if (LastWriteTime != CurrentFile.Timestamp) { CurrentFile.Hash = ComputeHashForFile(CurrentFilePath); CurrentFile.Timestamp = LastWriteTime; } CurrentFileLookup.Add(CurrentFile.Name, CurrentFile); } } // Also add all the untracked files which already exist, but weren't downloaded by this program foreach (DependencyFile TargetFile in TargetFiles.Values) { if (!CurrentFileLookup.ContainsKey(TargetFile.Name)) { string CurrentFilePath = Path.Combine(RootPath, TargetFile.Name); if (File.Exists(CurrentFilePath)) { WorkingFile CurrentFile = new WorkingFile(); CurrentFile.Name = TargetFile.Name; CurrentFile.Hash = ComputeHashForFile(CurrentFilePath); CurrentFile.Timestamp = File.GetLastWriteTimeUtc(CurrentFilePath).Ticks; CurrentFileLookup.Add(CurrentFile.Name, CurrentFile); } } } // Build a list of all the filtered target files List <DependencyFile> FilteredTargetFiles = new List <DependencyFile>(); foreach (DependencyFile TargetFile in TargetFiles.Values) { if (!IsExcludedFolder(TargetFile.Name, ExcludeFolders)) { FilteredTargetFiles.Add(TargetFile); } } // Create a list of files which need to be updated, and a list of the executable files in the List <DependencyFile> FilesToDownload = new List <DependencyFile>(); // Create a new working manifest for the working directory, moving over files that we already have. Add any missing dependencies into the download queue. WorkingManifest NewWorkingManifest = new WorkingManifest(); foreach (DependencyFile TargetFile in FilteredTargetFiles) { WorkingFile NewFile; if (CurrentFileLookup.TryGetValue(TargetFile.Name, out NewFile) && NewFile.Hash == TargetFile.Hash) { // Update the expected hash to match what we're looking for NewFile.ExpectedHash = TargetFile.Hash; // Move the existing file to the new working set CurrentFileLookup.Remove(NewFile.Name); } else { // Create a new working file NewFile = new WorkingFile(); NewFile.Name = TargetFile.Name; NewFile.ExpectedHash = TargetFile.Hash; // Add it to the download list FilesToDownload.Add(TargetFile); } NewWorkingManifest.Files.Add(NewFile); } // Print out everything that we'd change in a dry run if (bDryRun) { HashSet <string> NewFiles = new HashSet <string>(FilesToDownload.Select(x => x.Name)); foreach (string RemoveFile in CurrentFileLookup.Keys.Where(x => !NewFiles.Contains(x))) { Log.WriteLine("Remove {0}", RemoveFile); } foreach (string UpdateFile in CurrentFileLookup.Keys.Where(x => NewFiles.Contains(x))) { Log.WriteLine("Update {0}", UpdateFile); } foreach (string AddFile in NewFiles.Where(x => !CurrentFileLookup.ContainsKey(x))) { Log.WriteLine("Add {0}", AddFile); } return(true); } // Delete any files which are no longer needed List <WorkingFile> TamperedFiles = new List <WorkingFile>(); foreach (WorkingFile FileToRemove in CurrentFileLookup.Values) { if (Overwrite != OverwriteMode.Force && FileToRemove.Hash != FileToRemove.ExpectedHash) { TamperedFiles.Add(FileToRemove); } else if (!SafeDeleteFile(Path.Combine(RootPath, FileToRemove.Name))) { return(false); } } // Warn if there were any files that have been tampered with, and allow the user to choose whether to overwrite them bool bOverwriteTamperedFiles = true; if (TamperedFiles.Count > 0 && Overwrite != OverwriteMode.Force) { // List the files that have changed Log.WriteError("The following file(s) have been modified:"); foreach (WorkingFile TamperedFile in TamperedFiles) { Log.WriteError(" {0}", TamperedFile.Name); } // Figure out whether to overwrite the files if (Overwrite == OverwriteMode.Unchanged) { Log.WriteError("Re-run with the --force parameter to overwrite them."); bOverwriteTamperedFiles = false; } else { Log.WriteStatus("Would you like to overwrite your changes (y/n)? "); ConsoleKeyInfo KeyInfo = Console.ReadKey(false); bOverwriteTamperedFiles = (KeyInfo.KeyChar == 'y' || KeyInfo.KeyChar == 'Y'); Log.FlushStatus(); } } // Overwrite any tampered files, or remove them from the download list if (bOverwriteTamperedFiles) { foreach (WorkingFile TamperedFile in TamperedFiles) { if (!SafeDeleteFile(Path.Combine(RootPath, TamperedFile.Name))) { return(false); } } } else { foreach (WorkingFile TamperedFile in TamperedFiles) { DependencyFile TargetFile; if (TargetFiles.TryGetValue(TamperedFile.Name, out TargetFile)) { TargetFiles.Remove(TamperedFile.Name); FilesToDownload.Remove(TargetFile); } } } // Write out the new working manifest, so we can track any files that we're going to download. We always verify missing files on startup, so it's ok that things don't exist yet. if (!WriteWorkingManifest(WorkingManifestPath, TempWorkingManifestPath, NewWorkingManifest)) { return(false); } // If there's nothing to do, just print a simpler message and exit early if (FilesToDownload.Count > 0) { // Download all the new dependencies if (!DownloadDependencies(RootPath, FilesToDownload, TargetBlobs.Values, TargetPacks.Values, NumThreads, MaxRetries, ProxyUrl)) { return(false); } // Update all the timestamps and hashes for the output files foreach (WorkingFile NewFile in NewWorkingManifest.Files) { if (NewFile.Hash != NewFile.ExpectedHash) { string NewFileName = Path.Combine(RootPath, NewFile.Name); NewFile.Hash = NewFile.ExpectedHash; NewFile.Timestamp = File.GetLastWriteTimeUtc(NewFileName).Ticks; } } // Rewrite the manifest with the results if (!WriteWorkingManifest(WorkingManifestPath, TempWorkingManifestPath, NewWorkingManifest)) { return(false); } } // Update all the executable permissions if (!SetExecutablePermissions(RootPath, FilteredTargetFiles)) { return(false); } return(true); }