// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Diagnostics; using System.IO; using EpicGames.Core; using UnrealBuildBase; namespace UnrealBuildTool.Storage.Impl { /// /// Storage provider which uses the filesystem /// class FileSystemStorageProvider : IStorageProvider { /// /// Cached copy of the current process id /// static int ProcessId = Environment.ProcessId; /// /// Implements for writes to the backing storage /// class StorageReader : IStorageReader { public Stream? Stream { get; private set; } public bool IsValid => Stream != null; public StorageReader(FileReference Location) { if (FileReference.Exists(Location)) { Stream = FileReference.Open(Location, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete); } } public void Dispose() { if (Stream != null) { Stream.Dispose(); Stream = null; } } } /// /// Represents a write transaction to the cache. Commit() should be called once the transaction is complete. /// class StorageWriter : IStorageWriter { FileReference? TempLocation; FileReference FinalLocation; public Stream? Stream { get; private set; } public StorageWriter(FileReference Location) { FinalLocation = Location; DirectoryReference.CreateDirectory(FinalLocation.Directory); TempLocation = new FileReference(String.Format("{0}.{1}", Location.FullName, ProcessId)); Stream = FileReference.Open(TempLocation, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete); } public void Commit() { if (TempLocation == null) { throw new InvalidOperationException("Item has already been committed"); } Stream?.Close(); try { FileReference.Move(TempLocation, FinalLocation); } catch { FileReference.Delete(TempLocation); TempLocation = null; throw; } TempLocation = null; } public void Dispose() { if (Stream != null) { Stream.Dispose(); Stream = null; } if (TempLocation != null) { FileReference.Delete(TempLocation); TempLocation = null; } } } /// /// Attempts to open a file from the output cache /// /// Digest of the item to retrieve /// Reader interface for the file public IStorageReader CreateReader(ContentHash Digest) { return new StorageReader(GetFileForDigest(Digest)); } /// /// Opens a stream for writing into the cache. The digest /// /// public IStorageWriter CreateWriter(ContentHash Digest) { return new StorageWriter(GetFileForDigest(Digest)); } /// /// Gets the filename on disk to use for a particular digest /// /// The digest to find a filename for /// Filename to use for the given digest static FileReference GetFileForDigest(ContentHash Digest) { string DigestText = Digest.ToString(); return FileReference.Combine(Unreal.EngineDirectory, "Saved", "UnrealBuildTool", "Cache", String.Format("{0}/{1}/{2}/{3}.bin", DigestText[0], DigestText[1], DigestText[2], DigestText)); } } }