Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<DownloadObjectsRequest>.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<ActionResult> HandleAsync([FromBody] DownloadObjectsRequest request, CancellationToken cancellationToken = default)
{
var result = await _mediator.Send(request, cancellationToken);

return result.Match<ActionResult>(
zip => File(zip, "application/zip", "files.zip"),
notFound => new NoContentResult()
);
}
}
Original file line number Diff line number Diff line change
@@ -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<DownloadObjectsRequest, OneOf<byte[], NotFound>>
{
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<OneOf<byte[], NotFound>> Handle(DownloadObjectsRequest request, CancellationToken cancellationToken)
{
var objects = await _session.Query<ObjectProperties>()
.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();
}
}
Original file line number Diff line number Diff line change
@@ -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<OneOf<byte[], NotFound>>
{
[Required]
public required List<string> ObjectIds { get; set; }
}

Loading