// 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));
}
}
}