diff --git a/src/SF.PhotoPixels.API/Endpoints/PhotosEndpoints/DownloadObjects.cs b/src/SF.PhotoPixels.API/Endpoints/PhotosEndpoints/DownloadObjects.cs new file mode 100644 index 0000000..bfcea38 --- /dev/null +++ b/src/SF.PhotoPixels.API/Endpoints/PhotosEndpoints/DownloadObjects.cs @@ -0,0 +1,33 @@ +using Ardalis.ApiEndpoints; +using Mediator; +using Microsoft.AspNetCore.Mvc; +using SF.PhotoPixels.Application.Query.PhotoStorage.LoadMedia; +using Swashbuckle.AspNetCore.Annotations; + +namespace SF.PhotoPixels.API.Endpoints.PhotosEndpoints; + +public class DownloadObjects : EndpointBaseAsync.WithRequest.WithoutResult +{ + private readonly IMediator _mediator; + + public DownloadObjects(IMediator mediator) + { + _mediator = mediator; + } + + [HttpPost("/object/downloadZip")] + [SwaggerOperation( + Summary = "Download selected photos/videos as a zip file", + Description = "Download photos/videos from the server as a zip file", + Tags = new[] { "Object operations" }), + ] + public override async Task HandleAsync([FromBody] DownloadObjectsRequest request, CancellationToken cancellationToken = default) + { + var result = await _mediator.Send(request, cancellationToken); + + return result.Match( + zip => File(zip, "application/zip", "files.zip"), + notFound => new NoContentResult() + ); + } +} diff --git a/src/SF.PhotoPixels.Application/Query/PhotoStorage/LoadMedia/DownloadObjectsHandler.cs b/src/SF.PhotoPixels.Application/Query/PhotoStorage/LoadMedia/DownloadObjectsHandler.cs new file mode 100644 index 0000000..628c406 --- /dev/null +++ b/src/SF.PhotoPixels.Application/Query/PhotoStorage/LoadMedia/DownloadObjectsHandler.cs @@ -0,0 +1,54 @@ +using System.IO.Compression; +using Marten; +using Mediator; +using OneOf; +using OneOf.Types; +using SF.PhotoPixels.Application.Core; +using SF.PhotoPixels.Domain.Entities; +using SF.PhotoPixels.Infrastructure.Storage; + +namespace SF.PhotoPixels.Application.Query.PhotoStorage.LoadMedia; + +public class DownloadObjectsHandler : IRequestHandler> +{ + private readonly IExecutionContextAccessor _executionContextAccessor; + private readonly IDocumentSession _session; + private readonly IObjectStorage _objectStorage; + + public DownloadObjectsHandler(IExecutionContextAccessor executionContextAccessor, IDocumentSession session, IObjectStorage objectStorage) + { + _executionContextAccessor = executionContextAccessor; + _session = session; + _objectStorage = objectStorage; + } + + public async ValueTask> Handle(DownloadObjectsRequest request, CancellationToken cancellationToken) + { + var objects = await _session.Query() + .Where(obj => request.ObjectIds.Contains(obj.Id) && _executionContextAccessor.UserId == obj.UserId) + .Select(x => new { x.Hash, x.Extension, x.Name }) + .ToListAsync(cancellationToken); + + if (objects.Count == 0) + return new NotFound(); + + var userFolders = _objectStorage.GetUserFolders(_executionContextAccessor.UserId); + + using var memoryStream = new MemoryStream(); + using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) + { + foreach (var obj in objects) + { + var filePath = Path.Combine(userFolders.ObjectFolder, $"{obj.Hash}.{obj.Extension}"); + var entry = zipArchive.CreateEntry(obj.Name, CompressionLevel.Optimal); + + await using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true); + await using var entryStream = entry.Open(); + await fileStream.CopyToAsync(entryStream, cancellationToken); + } + } + + memoryStream.Position = 0; + return memoryStream.ToArray(); + } +} \ No newline at end of file diff --git a/src/SF.PhotoPixels.Application/Query/PhotoStorage/LoadMedia/DownloadObjectsRequest.cs b/src/SF.PhotoPixels.Application/Query/PhotoStorage/LoadMedia/DownloadObjectsRequest.cs new file mode 100644 index 0000000..6fe52b7 --- /dev/null +++ b/src/SF.PhotoPixels.Application/Query/PhotoStorage/LoadMedia/DownloadObjectsRequest.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using Mediator; +using OneOf; +using OneOf.Types; + +namespace SF.PhotoPixels.Application.Query.PhotoStorage.LoadMedia; + +public class DownloadObjectsRequest : IRequest> +{ + [Required] + public required List ObjectIds { get; set; } +} +