// 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.Horde.Storage; namespace Jupiter.Implementation { public enum LastAccessTrackingFlags { DoTracking = 0, SkipTracking = 1, } public interface IBlobStore { Task PutObjectAsync(NamespaceId ns, byte[] blob, BlobId identifier); Task PutObjectAsync(NamespaceId ns, ReadOnlyMemory blob, BlobId identifier); Task PutObjectAsync(NamespaceId ns, Stream content, BlobId identifier); Task GetObjectAsync(NamespaceId ns, BlobId blob, LastAccessTrackingFlags flags = LastAccessTrackingFlags.DoTracking, bool supportsRedirectUri = false); Task ExistsAsync(NamespaceId ns, BlobId blob, bool forceCheck = false); // Delete a object Task DeleteObjectAsync(NamespaceId ns, BlobId blob); // Delete a object from multiple namespaces at once Task DeleteObjectAsync(IEnumerable ns, BlobId blob); // delete the whole namespace Task DeleteNamespaceAsync(NamespaceId ns); IAsyncEnumerable<(BlobId, DateTime)> ListObjectsAsync(NamespaceId ns); Task PutObjectWithRedirectAsync(NamespaceId ns, BlobId identifier); Task GetObjectByRedirectAsync(NamespaceId ns, BlobId blob); Task GetObjectMetadataAsync(NamespaceId ns, BlobId blobId); Task CopyBlobAsync(NamespaceId ns, NamespaceId targetNamespace, BlobId blobId); } public interface IMultipartBlobStore { Task StartMultipartUploadAsync(NamespaceId ns, string blobName); Task CompleteMultipartUploadAsync(NamespaceId ns, string path, string uploadId, List partIds); Task PutMultipartPartAsync(NamespaceId ns, string blobName, string uploadId, string partIdentifier, byte[] blob); Task GetWriteRedirectForPartAsync(NamespaceId ns, string blobName, string uploadId, string partIdentifier); Task GetMultipartObjectByNameAsync(NamespaceId ns, string blobName); Task RenameMultipartBlobAsync(NamespaceId ns, string blobName, BlobId blobId); List GetMultipartRanges(NamespaceId ns, string uploadId, ulong blobLength); MultipartLimits GetMultipartLimits(NamespaceId ns); } public class MissingMultipartPartsException : Exception { public MissingMultipartPartsException(List missingParts) { MissingParts = missingParts; } public List MissingParts { get; init; } } public record MultipartLimits { public uint MinChunkSize { get; set; } public uint IdealChunkSize { get; set; } public int MaxCountOfChunks { get; set; } } public record MultipartByteRange { public ulong FirstByte { get; set; } public ulong LastByte { get; set; } public string PartId { get; set; } = null!; } public interface IStorageBackend { Task WriteAsync(string path, Stream content, CancellationToken cancellationToken = default); Task TryReadAsync(string path, LastAccessTrackingFlags flags = LastAccessTrackingFlags.DoTracking, CancellationToken cancellationToken = default); Task ExistsAsync(string path, CancellationToken cancellationToken = default); Task DeleteAsync(string path, CancellationToken cancellationToken = default); IAsyncEnumerable<(string, DateTime)> ListAsync(CancellationToken cancellationToken = default); } public class BlobNotFoundException : Exception { public NamespaceId Ns { get; } public BlobId Blob { get; } public BlobNotFoundException(NamespaceId ns, BlobId blob) : base($"No Blob in Namespace {ns} with id {blob}") { Ns = ns; Blob = blob; } public BlobNotFoundException(NamespaceId ns, BlobId blob, string message) : base(message) { Ns = ns; Blob = blob; } } public class BlobReplicationException : BlobNotFoundException { public BlobReplicationException(NamespaceId ns, BlobId blob, string message) : base(ns, blob, message) { } } public class BlobToLargeException : Exception { public BlobId Blob { get; } public BlobToLargeException(BlobId blob) : base($"Blob {blob} was to large to cache") { Blob = blob; } } public class ResourceHasToManyRequestsException : Exception { public ResourceHasToManyRequestsException(Exception originalException) : base($"To many requests to resource", originalException) { } } }