/// <summary> /// Copies all the files and directories from the source directory recursively into this directory. /// </summary> /// <param name="this">The destination directory.</param> /// <param name="source">The source directory.</param> /// <param name="overwrite">Whether or not to overwrite existing files.</param> /// <returns>An enumerable of copied files.</returns> public static async IAsyncEnumerable <IFile> CopyFromDirectory(this IDirectory @this, IReadOnlyDirectory source, bool overwrite) { if (@this == source) { yield break; } // Do the parent directory foreach (var f in source.EnumerateFiles()) { yield return(await @this.CopyFromAsync(f, overwrite)); } var queuedDirs = source.EnumerateDirectories() .Select(d => (@this, d)).ToList(); // BFS over all the children. Queue <(IDirectory, IReadOnlyDirectory)> dirsToProcess = new (queuedDirs); while (dirsToProcess.Count > 0) { var(parent, src) = dirsToProcess.Dequeue(); var dst = parent.OpenDirectory(src.Name); foreach (var f in src.EnumerateFiles()) { yield return(await dst.CopyFromAsync(f, overwrite)); } var children = src.EnumerateDirectories() .Select(d => (dst, d)).ToList(); foreach (var childDirectory in children) { dirsToProcess.Enqueue(childDirectory); } } }
public async override Task <ISaveGame> CreateSave(IReadOnlyDirectory saveContents) { if (!this.ProfileRoot.ContainsDirectory("base")) { await this.CreateBaseSave(saveContents); } using var rollingHash = new RollingHash(32); // setup var newGuid = Guid.NewGuid(); var saveName = $"{DateTimeOffset.UtcNow.ToString(DateFormat)}-{newGuid}"; var saveDirectory = this.ProfileRoot.OpenDirectory(saveName); var contentDirectory = saveDirectory.OpenDirectory("content"); // diff is for anything that exists in the base directory var diffDir = contentDirectory.OpenDirectory("diff"); // copy is for anything that does not and can not be diffed. var copyDir = contentDirectory.OpenDirectory("copy"); // Traverse base directory in tandem with saveContents var baseDir = this.ProfileRoot.OpenDirectory("base/content").AsReadOnly(); foreach (var f in saveContents.EnumerateFiles()) { if (!baseDir.ContainsFile(f.Name)) { await copyDir.CopyFromAsync(f); continue; } using var targetStream = f.OpenReadStream(); using var baseStream = baseDir.OpenFile(f.Name).OpenReadStream(); using var outStream = diffDir.OpenFile(f.Name).OpenStream(); using var decoder = new VcEncoder(baseStream, targetStream, outStream, rollingHash: rollingHash, blockSize: 32); VCDiffResult result = await decoder.EncodeAsync(); if (result != VCDiffResult.SUCCESS) { throw new IOException($"Failed to encode delta for {f.Name}"); } } foreach (var d in saveContents.EnumerateDirectories().Where(d => !baseDir.ContainsDirectory(d.Name))) { // Copy all directories not in the base. await foreach (var _ in copyDir.OpenDirectory(d.Name).CopyFromDirectory(d)) { } } var queuedDirs = (from targetDir in saveContents.EnumerateDirectories() where baseDir.ContainsDirectory(targetDir.Name) select(diffDir, baseDir.OpenDirectory(targetDir.Name), targetDir)).ToList(); Queue <(IDeletableDirectory parentDir, IReadOnlyDirectory baseDir, IReadOnlyDirectory targetDir)> dirsToProcess = new(queuedDirs); while (dirsToProcess.Count > 0) { var(parent, src, diff) = dirsToProcess.Dequeue(); var dst = parent.OpenDirectory(src.Name); foreach (var f in src.EnumerateFiles()) { if (!diff.ContainsFile(f.Name)) { continue; } using var baseStream = f.OpenReadStream(); using var targetStream = diff.OpenFile(f.Name).OpenReadStream(); using var outStream = dst.OpenFile(f.Name).OpenStream(); using var decoder = new VcEncoder(baseStream, targetStream, outStream, rollingHash: rollingHash, blockSize: 32); VCDiffResult result = await decoder.EncodeAsync(); if (result != VCDiffResult.SUCCESS) { throw new IOException($"Failed to decode delta for {f.Name}"); } } var children = from targetDir in diff.EnumerateDirectories() where src.ContainsDirectory(targetDir.Name) select(dst, src.OpenDirectory(targetDir.Name), targetDir); foreach (var childDirectory in children) { dirsToProcess.Enqueue(childDirectory); } } this.ProfileRoot.OpenFile("latest").WriteAllText(saveName, Encoding.UTF8); return(this.GetSave(saveDirectory) !); }