// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using EpicGames.Core; using EpicGames.Serialization; namespace EpicGames.Perforce.Managed { /// /// Stores the contents of a stream in memory /// public class StreamSnapshotFromMemory : StreamSnapshot { /// /// The current signature for saved directory objects /// static readonly byte[] s_currentSignature = { (byte)'W', (byte)'S', (byte)'D', 5 }; /// /// The root digest /// public override StreamTreeRef Root { get; } /// /// Map of digest to directory /// public IReadOnlyDictionary HashToTree { get; } /// /// Constructor /// /// /// public StreamSnapshotFromMemory(StreamTreeRef root, Dictionary hashToTree) { Root = root; HashToTree = hashToTree; } /// /// Constructor /// /// public StreamSnapshotFromMemory(StreamTreeBuilder builder) { Dictionary hashToTree = new Dictionary(); Root = builder.EncodeRef(tree => EncodeObject(tree, hashToTree)); HashToTree = hashToTree; } /// /// Serialize to a compact binary object /// /// /// /// static IoHash EncodeObject(StreamTree tree, Dictionary hashToTree) { CbObject @object = tree.ToCbObject(); IoHash hash = @object.GetHash(); hashToTree[hash] = @object; return hash; } /// public override StreamTree Lookup(StreamTreeRef treeRef) { return new StreamTree(treeRef.Path, HashToTree[treeRef.Hash]); } /// /// Load a stream directory from a file on disk /// /// File to read from /// Base path to use if missing inside loaded file /// Cancellation token /// New StreamDirectoryInfo object public static async Task TryLoadAsync(FileReference inputFile, Utf8String defaultBasePath, CancellationToken cancellationToken) { byte[] data = await FileReference.ReadAllBytesAsync(inputFile, cancellationToken); if (!data.AsSpan().StartsWith(s_currentSignature)) { return null; } CbObject rootObj = new CbObject(data.AsMemory(s_currentSignature.Length)); CbObject rootObj2 = rootObj["root"].AsObject(); Utf8String rootPath = rootObj2["path"].AsUtf8String(defaultBasePath); StreamTreeRef root = new StreamTreeRef(rootPath, rootObj2); CbArray array = rootObj["items"].AsArray(); Dictionary hashToTree = new Dictionary(array.Count); foreach (CbField element in array) { CbObject objectElement = element.AsObject(); IoHash hash = objectElement["hash"].AsHash(); CbObject tree = objectElement["tree"].AsObject(); hashToTree[hash] = tree; } return new StreamSnapshotFromMemory(root, hashToTree); } /// /// Saves the contents of this object to disk /// /// The output file to write to /// public async Task SaveAsync(FileReference outputFile, Utf8String basePath) { CbWriter writer = new CbWriter(); writer.BeginObject(); writer.BeginObject("root"); if (Root.Path != basePath) { writer.WriteUtf8String("path", Root.Path); } Root.Write(writer); writer.EndObject(); writer.BeginArray("items"); foreach ((IoHash hash, CbObject tree) in HashToTree) { writer.BeginObject(); writer.WriteHash("hash", hash); writer.WriteObject("tree", tree); writer.EndObject(); } writer.EndArray(); writer.EndObject(); byte[] data = writer.ToByteArray(); using (FileStream outputStream = FileReference.Open(outputFile, FileMode.Create, FileAccess.Write, FileShare.Read)) { await outputStream.WriteAsync(s_currentSignature, 0, s_currentSignature.Length); await outputStream.WriteAsync(data, 0, data.Length); } } } }