// 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.Horde.Storage; namespace EpicGames.Horde.Compute { /// /// Storage backend which can read bundles over a compute channel /// public sealed class AgentStorageBackend : IStorageBackend, IDisposable { readonly AgentMessageChannel _channel; readonly SemaphoreSlim _semaphore; /// public bool SupportsRedirects => false; /// /// Constructor /// /// public AgentStorageBackend(AgentMessageChannel channel) { _channel = channel; _semaphore = new SemaphoreSlim(1); } /// public void Dispose() { _semaphore.Dispose(); } #region Blobs /// public async Task OpenBlobAsync(BlobLocator locator, int offset, int? length, CancellationToken cancellationToken = default) { ReadOnlyMemory data = await ReadBlobInternalAsync(locator, offset, length, cancellationToken); return new ReadOnlyMemoryStream(data); } /// public async Task> ReadBlobAsync(BlobLocator locator, int offset, int? length, CancellationToken cancellationToken = default) { ReadOnlyMemory data = await ReadBlobInternalAsync(locator, offset, length, cancellationToken); return ReadOnlyMemoryOwner.Create(data); } async ValueTask> ReadBlobInternalAsync(BlobLocator locator, int offset, int? length, CancellationToken cancellationToken = default) { await _semaphore.WaitAsync(cancellationToken); try { // TODO: Want to pass 0 for length to read here (meaning entire blob), but older streams misinterpret this as a 0 byte read. return await _channel.ReadBlobAsync(locator.ToString(), offset, length ?? 128 * 1024 * 1024, cancellationToken); } finally { _semaphore.Release(); } } /// public Task WriteBlobAsync(BlobLocator locator, Stream stream, IReadOnlyCollection? imports, CancellationToken cancellationToken = default) => throw new NotSupportedException(); /// public Task WriteBlobAsync(Stream stream, IReadOnlyCollection? imports, string? basePath = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); /// public ValueTask TryGetBlobReadRedirectAsync(BlobLocator locator, CancellationToken cancellationToken = default) => default; /// public ValueTask TryGetBlobWriteRedirectAsync(BlobLocator locator, IReadOnlyCollection? imports = null, CancellationToken cancellationToken = default) => default; /// public ValueTask<(BlobLocator, Uri)?> TryGetBlobWriteRedirectAsync(IReadOnlyCollection? imports = null, string? prefix = null, CancellationToken cancellationToken = default) => default; #endregion #region Aliases /// public Task AddAliasAsync(string name, BlobLocator locator, int rank, ReadOnlyMemory data, CancellationToken cancellationToken = default) => throw new NotSupportedException(); /// public Task RemoveAliasAsync(string name, BlobLocator locator, CancellationToken cancellationToken = default) => throw new NotSupportedException(); /// public Task FindAliasesAsync(string name, int? maxResults, CancellationToken cancellationToken = default) => throw new NotSupportedException(); #endregion #region Refs /// public Task DeleteRefAsync(RefName name, CancellationToken cancellationToken = default) => throw new NotSupportedException(); /// public Task TryReadRefAsync(RefName name, RefCacheTime cacheTime = default, CancellationToken cancellationToken = default) => throw new NotSupportedException(); /// public Task WriteRefAsync(RefName name, HashedBlobRefValue value, RefOptions? options = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); #endregion /// public async Task UpdateMetadataAsync(UpdateMetadataRequest request, CancellationToken cancellationToken = default) { foreach (AddAliasRequest addAlias in request.AddAliases) { await AddAliasAsync(addAlias.Name, addAlias.Target, addAlias.Rank, addAlias.Data, cancellationToken); } foreach (RemoveAliasRequest removeAlias in request.RemoveAliases) { await RemoveAliasAsync(removeAlias.Name, removeAlias.Target, cancellationToken); } foreach (AddRefRequest addRef in request.AddRefs) { await WriteRefAsync(addRef.RefName, new HashedBlobRefValue(addRef.Hash, addRef.Target), addRef.Options, cancellationToken); } foreach (RemoveRefRequest removeRef in request.RemoveRefs) { await DeleteRefAsync(removeRef.RefName, cancellationToken); } } /// public void GetStats(StorageStats stats) { } } }