Invia i log a parti e chiedi se includere le foto

Le foto venivano zippate tutte in un unico allegato: oltre i 100 MB i servizi
rifiutavano l'invio (MaxUploadSizeExceededException) e, costruendo tutto in RAM
sul thread della UI, il dispositivo si bloccava.

- Invio diviso in più email da massimo ~40 MB ciascuna
- Zip scritti su disco una parte alla volta e caricati in streaming
- Preparazione in background, avanzamento "parte X di N"
- Richiesta se includere le foto, con il loro peso (default: solo i log)
- Database inviato insieme al file -wal
- Avviso subito se si è offline

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MarcoEandClaude Opus 5.5 committed 2026-09-28 12:10:19 +02:00
1 parent a4e5709be6
commit 9799f6b0c7
9 files changed
+403 -183

No files matched your search

+12 -9
View File
@@ -6,28 +6,31 @@ public class SendEmailDto
{
[JsonPropertyName("from")]
public string? From { get; set; }
[JsonPropertyName("fromName")]
public string? FromName { get; set; }
[JsonPropertyName("to")]
public string? To { get; set; }
[JsonPropertyName("subject")]
public string? Subject { get; set; }
[JsonPropertyName("msgText")]
public string? MsgText { get; set; }
[JsonPropertyName("html")]
public bool IsHtml { get; set; }
[JsonPropertyName("attachments")]
// Gli allegati viaggiano come parti multipart "allegati", non dentro il JSON della richiesta.
[JsonIgnore]
public List<AttachmentsDto>? Attachments { get; set; }
public class AttachmentsDto
{
public string FileName { get; set; } = string.Empty;
public byte[] FileContent { get; set; } = [];
// Il file resta su disco e viene letto in streaming durante l'upload.
public string FilePath { get; set; } = string.Empty;
}
}
}
@@ -0,0 +1,39 @@
using System.Net;
using System.Net.Http.Headers;
namespace SteUp.Shared.Core.Helpers;
/// <summary>
/// Contenuto HTTP letto dal file durante l'invio, senza caricarlo in RAM. Il file viene riaperto
/// a ogni serializzazione, così regge anche un eventuale nuovo tentativo del client
/// (es. dopo il refresh del token).
/// </summary>
public sealed class FileHttpContent : HttpContent
{
private const int BufferSize = 81920;
private readonly string _path;
public FileHttpContent(string path, string mediaType)
{
_path = path;
Headers.ContentType = new MediaTypeHeaderValue(mediaType);
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) =>
SerializeToStreamAsync(stream, context, CancellationToken.None);
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context,
CancellationToken cancellationToken)
{
await using var file = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.Read,
BufferSize, useAsync: true);
await file.CopyToAsync(stream, BufferSize, cancellationToken);
}
protected override bool TryComputeLength(out long length)
{
length = new FileInfo(_path).Length;
return true;
}
}
@@ -26,5 +26,6 @@ public interface IFileManager
Task<(string originalUrl, string thumbUrl)> SaveAndCreateThumbAsync(byte[] bytes, string fileName,
CancellationToken ct = default);
List<SendEmailDto.AttachmentsDto> GetFileForExport();
Task<long> GetLogExportImagesSizeAsync(CancellationToken ct = default);
Task<ILogExport> PrepareLogExportAsync(bool includeImages, CancellationToken ct = default);
}
@@ -0,0 +1,15 @@
using SteUp.Shared.Core.Dto;
namespace SteUp.Shared.Core.Interface.System;
/// <summary>
/// Invio dei log diviso in parti: ogni parte diventa un'email separata, sotto i limiti di upload
/// dei servizi. Gli zip di una parte vengono creati solo quando la si chiede e cancellati alla
/// parte successiva; il dispose elimina tutto quello che resta su disco.
/// </summary>
public interface ILogExport : IAsyncDisposable
{
int PartCount { get; }
Task<List<SendEmailDto.AttachmentsDto>> BuildPartAsync(int index, CancellationToken ct = default);
}
@@ -1,9 +1,9 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text;
using System.Text.Json;
using IntegryApiClient.Core.Domain.RestClient.Contacts;
using Microsoft.Extensions.Logging;
using SteUp.Shared.Core.Dto;
using SteUp.Shared.Core.Helpers;
using SteUp.Shared.Core.Interface.IntegryApi;
namespace SteUp.Shared.Core.Services;
@@ -37,21 +37,18 @@ public class IntegryApiService(
public async Task SendEmail(SendEmailDto sendEmail)
{
var content = new MultipartFormDataContent();
using var content = new MultipartFormDataContent();
try
{
if (sendEmail.Attachments != null)
foreach (var a in sendEmail.Attachments ?? [])
{
foreach (var a in sendEmail.Attachments)
{
var fileContent = new ByteArrayContent(a.FileContent);
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
content.Add(fileContent, "allegati", a.FileName);
}
var mediaType = a.FileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)
? "application/zip"
: "application/octet-stream";
content.Add(new FileHttpContent(a.FilePath, mediaType), "allegati", a.FileName);
}
sendEmail.Attachments = null;
content.Add(
new StringContent(
JsonSerializer.Serialize(sendEmail),
@@ -69,9 +66,5 @@ public class IntegryApiService(
logger.LogError(e, e.Message);
throw;
}
finally
{
content.Dispose();
}
}
}