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:
1 parent
a4e5709be6
commit
9799f6b0c7
9 files changed
+403
-183
No files matched your search
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user