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
@@ -1,5 +1,4 @@
|
||||
using System.IO.Compression;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SteUp.Data.LocalDb;
|
||||
using SteUp.Shared.Core.Dto;
|
||||
using SteUp.Shared.Core.Entities;
|
||||
@@ -343,146 +342,57 @@ public class FileManager(
|
||||
private static string GetInspectionKey(Ispezione ispezione) =>
|
||||
$"{ispezione.CodMdep}_{ispezione.Data:ddMMyyyy}_{ispezione.Rilevatore.ToLower()}";
|
||||
|
||||
public List<SendEmailDto.AttachmentsDto> GetFileForExport()
|
||||
public Task<long> GetLogExportImagesSizeAsync(CancellationToken ct = default) =>
|
||||
Task.Run(() => RetrieveAttachedImages().Sum(x => x.File.Length), ct);
|
||||
|
||||
public Task<ILogExport> PrepareLogExportAsync(bool includeImages, CancellationToken ct = default)
|
||||
{
|
||||
var attachments = new List<SendEmailDto.AttachmentsDto>();
|
||||
|
||||
// 1) log file singolo (se ti serve ancora)
|
||||
var logFile = RetrieveLogFile();
|
||||
if (!logFile.IsNullOrEmpty())
|
||||
return Task.Run<ILogExport>(() =>
|
||||
{
|
||||
attachments.Add(new SendEmailDto.AttachmentsDto
|
||||
{
|
||||
FileName = $"logs_{DateTime.Today:yyyyMMdd}.zip",
|
||||
FileContent = CreateZipBytes(logFile!)
|
||||
});
|
||||
}
|
||||
var workDir = Path.Combine(FileSystem.CacheDirectory, "log_export");
|
||||
|
||||
// 2) database zip
|
||||
var dbZip = CreateDatabaseZipAttachment();
|
||||
if (dbZip != null) attachments.Add(dbZip);
|
||||
|
||||
// 3) Img zip
|
||||
var attachedInfo = new DirectoryInfo(Path.Combine(FileSystem.AppDataDirectory, "attached"));
|
||||
if (!attachedInfo.Exists) return attachments;
|
||||
|
||||
var attachedFiles = attachedInfo
|
||||
// Residui di un invio interrotto (app chiusa a metà): si riparte da zero.
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
Directory.CreateDirectory(workDir);
|
||||
|
||||
var logFiles = RetrieveLogFiles()
|
||||
.Select(f => new LogExport.ZipItem(f, f.Name))
|
||||
.ToList();
|
||||
|
||||
// Col journal WAL le modifiche recenti stanno ancora nel file -wal: senza, il database
|
||||
// inviato può non avere le ultime ispezioni.
|
||||
var dbPath = dbPathProvider.GetDbPath();
|
||||
var dbFiles = new[] { dbPath, $"{dbPath}-wal" }
|
||||
.Select(p => new FileInfo(p))
|
||||
.Where(f => f.Exists)
|
||||
.Select(f => new LogExport.ZipItem(f, f.Name))
|
||||
.ToList();
|
||||
|
||||
var images = includeImages ? RetrieveAttachedImages() : [];
|
||||
|
||||
return new LogExport(workDir, logFiles, dbFiles, images, logger);
|
||||
}, ct);
|
||||
}
|
||||
|
||||
private static List<FileInfo> RetrieveLogFiles()
|
||||
{
|
||||
var directory = new DirectoryInfo(Path.Combine(FileSystem.AppDataDirectory, "logs"));
|
||||
return directory.Exists ? directory.GetFiles().ToList() : [];
|
||||
}
|
||||
|
||||
private static List<LogExport.ZipItem> RetrieveAttachedImages()
|
||||
{
|
||||
var root = new DirectoryInfo(Path.Combine(FileSystem.AppDataDirectory, "attached"));
|
||||
if (!root.Exists) return [];
|
||||
|
||||
// Ordine per percorso: le foto della stessa ispezione finiscono nella stessa parte.
|
||||
// Il percorso relativo mantiene le cartelle dentro lo zip (separatore "/" per compatibilità).
|
||||
return root
|
||||
.EnumerateFiles("*", SearchOption.AllDirectories)
|
||||
.OrderBy(f => f.FullName, StringComparer.Ordinal)
|
||||
.Select(f => new LogExport.ZipItem(f,
|
||||
Path.GetRelativePath(root.FullName, f.FullName).Replace('\\', '/')))
|
||||
.ToList();
|
||||
|
||||
if (attachedFiles.Count > 0)
|
||||
{
|
||||
attachments.Add(new SendEmailDto.AttachmentsDto
|
||||
{
|
||||
FileName = $"immagini_allegate_{DateTime.Today:yyyyMMdd}.zip",
|
||||
FileContent = CreateZipBytes(attachedInfo.FullName, attachedFiles)
|
||||
});
|
||||
}
|
||||
|
||||
return attachments;
|
||||
}
|
||||
|
||||
private static List<FileInfo>? RetrieveLogFile()
|
||||
{
|
||||
var appDataPath = FileSystem.AppDataDirectory;
|
||||
var targetDirectory = Path.Combine(appDataPath, "logs");
|
||||
|
||||
var directory = new DirectoryInfo(targetDirectory);
|
||||
|
||||
List<FileInfo>? files = null;
|
||||
|
||||
if (directory.Exists)
|
||||
files = directory.GetFiles().ToList();
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private SendEmailDto.AttachmentsDto? CreateDatabaseZipAttachment()
|
||||
{
|
||||
var files = new[]
|
||||
{
|
||||
new FileInfo(dbPathProvider.GetDbPath())
|
||||
};
|
||||
|
||||
// Filtra solo quelli esistenti
|
||||
var existingFiles = files.Where(f => f.Exists).ToList();
|
||||
|
||||
if (existingFiles.Count == 0)
|
||||
return null;
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
||||
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
|
||||
{
|
||||
foreach (var file in existingFiles)
|
||||
{
|
||||
var entry = archive.CreateEntry(file.Name, CompressionLevel.Optimal);
|
||||
|
||||
using var entryStream = entry.Open();
|
||||
using var fileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
fileStream.CopyTo(entryStream);
|
||||
}
|
||||
}
|
||||
|
||||
return new SendEmailDto.AttachmentsDto
|
||||
{
|
||||
FileName = $"database_{DateTime.Now:yyyyMMdd_HHmm}.zip",
|
||||
FileContent = memoryStream.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
private static byte[] CreateZipBytes(IEnumerable<FileInfo> files)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true))
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!file.Exists)
|
||||
continue;
|
||||
|
||||
// Nome dentro lo zip (evita path e collisioni minime)
|
||||
var entryName = file.Name;
|
||||
|
||||
var entry = archive.CreateEntry(entryName, CompressionLevel.Optimal);
|
||||
|
||||
using var entryStream = entry.Open();
|
||||
using var fileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
fileStream.CopyTo(entryStream);
|
||||
}
|
||||
}
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] CreateZipBytes(string rootDir, IEnumerable<FileInfo> files)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
|
||||
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true))
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!file.Exists)
|
||||
continue;
|
||||
|
||||
// Path relativo rispetto a rootDir -> mantiene le directory nello zip
|
||||
var relativePath = Path.GetRelativePath(rootDir, file.FullName);
|
||||
|
||||
// Zip usa "/" come separatore: normalizziamo per compatibilità
|
||||
var entryName = relativePath.Replace('\\', '/');
|
||||
|
||||
var entry = archive.CreateEntry(entryName, CompressionLevel.Optimal);
|
||||
|
||||
using var entryStream = entry.Open();
|
||||
using var fileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
fileStream.CopyTo(entryStream);
|
||||
}
|
||||
}
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user