using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Threading.Tasks; using FrostFS.Container; using FrostFS.Refs; using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; namespace FrostFS.SDK.ClientV2; internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientContext clientCtx) : ContextAccessor(clientCtx), ISessionProvider { private SessionProvider? sessions; public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx) { sessions ??= new(ClientContext); if (ClientContext.SessionCache.Cache != null && ClientContext.SessionCache.Cache.ContainsKey(ClientContext.SessionCacheKey)) { return (SessionToken)ClientContext.SessionCache.Cache[ClientContext.SessionCacheKey]; } return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false); } internal async Task GetContainerAsync(PrmContainerGet args) { GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, args.Context); var response = await service.GetAsync(request, null, args.Context.Deadline, args.Context.CancellationToken); Verifier.CheckResponse(response); return response.Body.Container.ToModel(); } internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args) { var ctx = args.Context!; ctx.OwnerId ??= ClientContext.Owner; ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) throw new ArgumentNullException(nameof(args), "Key is null"); if (ctx.OwnerId == null) throw new ArgumentException(nameof(ctx.OwnerId)); var request = new ListRequest { Body = new() { OwnerId = ctx.OwnerId.ToMessage() } }; request.AddMetaHeader(args.XHeaders); request.Sign(ctx.Key); var response = await service.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); foreach (var cid in response.Body.ContainerIds) { yield return new FrostFsContainerId(Base58.Encode(cid.Value.ToByteArray())); } } internal async Task CreateContainerAsync(PrmContainerCreate args) { var ctx = args.Context!; var grpcContainer = args.Container.GetContainer(); grpcContainer.OwnerId ??= ctx.OwnerId?.ToMessage(); grpcContainer.Version ??= ctx.Version?.ToMessage(); if (ctx.Key == null) throw new ArgumentNullException(nameof(args), "Key is null"); if (grpcContainer.OwnerId == null) throw new ArgumentException(nameof(grpcContainer.OwnerId)); if (grpcContainer.Version == null) throw new ArgumentException(nameof(grpcContainer.Version)); var request = new PutRequest { Body = new PutRequest.Types.Body { Container = grpcContainer, Signature = ctx.Key.SignRFC6979(grpcContainer) } }; var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); sessionToken.CreateContainerTokenContext( null, ContainerSessionContext.Types.Verb.Put, ctx.Key, ctx.GetPublicKeyCache()!); request.AddMetaHeader(args.XHeaders, sessionToken); request.Sign(ctx.Key); var response = await service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); await WaitForContainer(WaitExpects.Exists, response.Body.ContainerId, args.WaitParams, ctx).ConfigureAwait(false); return new FrostFsContainerId(response.Body.ContainerId); } internal async Task DeleteContainerAsync(PrmContainerDelete args) { var ctx = args.Context!; if (ctx.Key == null) throw new ArgumentNullException(nameof(args), "Key is null"); var request = new DeleteRequest { Body = new DeleteRequest.Types.Body { ContainerId = args.ContainerId.ToMessage(), Signature = ctx.Key.SignRFC6979(args.ContainerId.ToMessage().Value) } }; var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); sessionToken.CreateContainerTokenContext( request.Body.ContainerId, ContainerSessionContext.Types.Verb.Delete, ctx.Key, ctx.GetPublicKeyCache()!); request.AddMetaHeader(args.XHeaders, sessionToken); request.Sign(ctx.Key); var response = await service.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); await WaitForContainer(WaitExpects.Removed, request.Body.ContainerId, args.WaitParams, ctx) .ConfigureAwait(false); Verifier.CheckResponse(response); } private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, CallContext ctx) { if (ctx.Key == null) throw new ArgumentNullException(nameof(ctx), "Key is null"); var request = new GetRequest { Body = new GetRequest.Types.Body { ContainerId = id } }; request.AddMetaHeader(xHeaders); request.Sign(ctx.Key); return request; } private enum WaitExpects { Exists, Removed } private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, CallContext ctx) { var request = GetContainerRequest(id, null, ctx); async Task action() { var response = await service.GetAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); } await WaitFor(action, expect, waitParams).ConfigureAwait(false); } private static async Task WaitFor( Func action, WaitExpects expect, PrmWait? waitParams) { waitParams ??= PrmWait.DefaultParams; var deadLine = waitParams.GetDeadline(); while (true) { try { await action().ConfigureAwait(false); if (expect == WaitExpects.Exists) return; if (DateTime.UtcNow >= deadLine) throw new TimeoutException(); await Task.Delay(waitParams.PollInterval).ConfigureAwait(false); } catch (FrostFsResponseException ex) { if (DateTime.UtcNow >= deadLine) throw new TimeoutException(); if (ex.Status?.Code != FrostFsStatusCode.ContainerNotFound) throw; if (expect == WaitExpects.Removed) return; await Task.Delay(waitParams.PollInterval).ConfigureAwait(false); } } } }