From 2d938fb21057a5e36dc351d2cb811a46ecdedea6 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Wed, 4 Mar 2026 11:51:42 +0100 Subject: [PATCH] Fix gestione allegati e creato metodo di esportazione log --- .../EntityServices/IspezioniService.cs | 11 + SteUp.Maui/App.xaml.cs | 6 +- SteUp.Maui/Core/CoreModule.cs | 15 + SteUp.Maui/Core/Logger/FileLogger.cs | 185 ++++++++ SteUp.Maui/Core/Logger/FileLoggerProvider.cs | 14 + SteUp.Maui/Core/Services/AttachedService.cs | 315 +------------ SteUp.Maui/Core/Services/FileManager.cs | 442 ++++++++++++++++++ SteUp.Maui/Core/Utility/UtilityFile.cs | 23 + .../GlobalExceptionHandler.cs | 37 ++ SteUp.Maui/MauiProgram.cs | 1 + .../Components/Pages/IspezionePage.razor | 11 +- SteUp.Shared/Components/Pages/LoginPage.razor | 7 +- SteUp.Shared/Components/Pages/UserPage.razor | 63 ++- .../SingleElements/Card/SchedaCard.razor | 6 +- .../Modal/ExceptionModal/ExceptionModal.razor | 7 +- .../Modal/ModalFormScheda.razor | 57 ++- .../Modal/ModalSelectShop.razor | 25 +- .../Core/Data/Contracts/ISteupDataService.cs | 1 + SteUp.Shared/Core/Data/SteupDataService.cs | 2 +- SteUp.Shared/Core/Dto/SendEmailDto.cs | 33 ++ .../IntegryApi/IIntegryApiService.cs | 2 + .../Interface/LocalDb/IIspezioniService.cs | 1 + .../Core/Interface/System/IAttachedService.cs | 21 +- .../Core/Interface/System/IFileManager.cs | 29 ++ .../Core/Services/IntegryApiService.cs | 49 +- .../Core/Services/IntegrySteupService.cs | 7 +- 26 files changed, 986 insertions(+), 384 deletions(-) create mode 100644 SteUp.Maui/Core/Logger/FileLogger.cs create mode 100644 SteUp.Maui/Core/Logger/FileLoggerProvider.cs create mode 100644 SteUp.Maui/Core/Services/FileManager.cs create mode 100644 SteUp.Maui/Core/Utility/UtilityFile.cs create mode 100644 SteUp.Maui/Core/UtilityException/GlobalExceptionHandler.cs create mode 100644 SteUp.Shared/Core/Dto/SendEmailDto.cs create mode 100644 SteUp.Shared/Core/Interface/System/IFileManager.cs diff --git a/SteUp.Data/LocalDb/EntityServices/IspezioniService.cs b/SteUp.Data/LocalDb/EntityServices/IspezioniService.cs index fc3b4f5..0ab43cd 100644 --- a/SteUp.Data/LocalDb/EntityServices/IspezioniService.cs +++ b/SteUp.Data/LocalDb/EntityServices/IspezioniService.cs @@ -182,6 +182,17 @@ public class IspezioniService(AppDbContext db) : IIspezioniService return true; } + public async Task UpdateFileListSchedaAsync(int schedaId, List? imageNames) + { + var scheda = await db.Schede.FirstOrDefaultAsync(x => x.Id == schedaId); + if (scheda is null) return false; + + scheda.ImageNames = imageNames; + db.Schede.Update(scheda); + await db.SaveChangesAsync(); + return true; + } + public async Task DeleteSchedaAsync(int schedaId) { var scheda = await db.Schede.FirstOrDefaultAsync(x => x.Id == schedaId); diff --git a/SteUp.Maui/App.xaml.cs b/SteUp.Maui/App.xaml.cs index 3c9760e..86ceb91 100644 --- a/SteUp.Maui/App.xaml.cs +++ b/SteUp.Maui/App.xaml.cs @@ -1,10 +1,14 @@ +using Microsoft.Extensions.Logging; +using SteUp.Maui.Core.UtilityException; + namespace SteUp.Maui { public partial class App { - public App() + public App(ILogger logger) { InitializeComponent(); + GlobalExceptionHandler.Register(logger); } protected override Window CreateWindow(IActivationState? activationState) diff --git a/SteUp.Maui/Core/CoreModule.cs b/SteUp.Maui/Core/CoreModule.cs index 3a9131c..139ee4d 100644 --- a/SteUp.Maui/Core/CoreModule.cs +++ b/SteUp.Maui/Core/CoreModule.cs @@ -1,8 +1,10 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using SteUp.Data.LocalDb; using SteUp.Data.LocalDb.EntityServices; +using SteUp.Maui.Core.Logger; using SteUp.Maui.Core.Services; using SteUp.Maui.Core.System; using SteUp.Maui.Core.System.Network; @@ -45,6 +47,7 @@ public static class CoreModule { builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); } @@ -75,5 +78,17 @@ public static class CoreModule builder.Services.AddSingleton(); builder.Services.AddSingleton(); } + + public void RegisterLoggerServices() + { + var logPath = Path.Combine(FileSystem.AppDataDirectory, "logs"); + const string logFilePrefix = "SteUp-log"; + + builder.Services.AddLogging(loggingBuilder => + { + loggingBuilder.AddProvider(new FileLoggerProvider(logPath, logFilePrefix)); + loggingBuilder.SetMinimumLevel(LogLevel.Information); + }); + } } } \ No newline at end of file diff --git a/SteUp.Maui/Core/Logger/FileLogger.cs b/SteUp.Maui/Core/Logger/FileLogger.cs new file mode 100644 index 0000000..d999874 --- /dev/null +++ b/SteUp.Maui/Core/Logger/FileLogger.cs @@ -0,0 +1,185 @@ +using System.Diagnostics; +using Microsoft.Extensions.Logging; +using System.Globalization; +using System.Text; + +namespace SteUp.Maui.Core.Logger; + +public class FileLogger : ILogger +{ + private readonly string _path; + private readonly string _fileNamePrefix; + private readonly string _categoryName; + private readonly Lock _lock = new(); + private readonly int _retentionDays; + private string? _currentFileName; + private DateTime _currentFileDate; + private DateTime _lastCleanupDate; + + public FileLogger(string path, string fileNamePrefix, string categoryName, int retentionDays = 60) + { + _path = path; + _fileNamePrefix = fileNamePrefix; + _retentionDays = retentionDays; + _lastCleanupDate = DateTime.MinValue; + _categoryName = categoryName; + + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + + UpdateCurrentFileName(); + TryCleanOldLogs(); + } + + /// + /// Elimina i log più vecchi di giorni. + /// Viene eseguita al massimo una volta al giorno. + /// + private void ClearOldLogs() + { + try + { + var cutoff = DateTime.Now.Date.AddDays(-_retentionDays); + var logFiles = Directory.GetFiles(_path, $"{_fileNamePrefix}-*.log"); + + foreach (var file in logFiles) + { + try + { + var fileName = Path.GetFileNameWithoutExtension(file); + + var datePart = fileName[(_fileNamePrefix.Length + 1)..]; + + if (!DateTime.TryParseExact(datePart, "yyyy-MM-dd", + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var fileDate) || fileDate >= cutoff) continue; + File.Delete(file); + Debug.WriteLine($"[FileLogger] Log eliminato: {file}"); + } + catch (Exception ex) + { + Debug.WriteLine($"[FileLogger] Errore durante l'eliminazione del file {file}: {ex.Message}"); + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"[FileLogger] Errore durante la pulizia dei log: {ex.Message}"); + } + } + + /// + /// Esegue la pulizia dei log solo se non è già stata eseguita oggi. + /// + private void TryCleanOldLogs() + { + var today = DateTime.Now.Date; + if (_lastCleanupDate == today) return; + _lastCleanupDate = today; + ClearOldLogs(); + } + + private void UpdateCurrentFileName() + { + var today = DateTime.Now.Date; + if (_currentFileName != null && _currentFileDate == today) return; + _currentFileDate = today; + _currentFileName = $"{_fileNamePrefix}-{today:yyyy-MM-dd}.log"; + } + + public IDisposable? BeginScope(TState state) => null; + + public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; + + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) + { + if (!IsEnabled(logLevel)) + return; + + try + { + lock (_lock) + { + UpdateCurrentFileName(); + TryCleanOldLogs(); + + if (_currentFileName == null) return; + + var fullPath = Path.Combine(_path, _currentFileName); + var logEntry = BuildLogEntry(logLevel, eventId, state, exception, formatter); + + File.AppendAllText(fullPath, logEntry + Environment.NewLine + Environment.NewLine); + Debug.WriteLine($"[FileLogger] {logEntry}"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"[FileLogger] Errore durante la scrittura del log: {ex}"); + } + } + + private string BuildLogEntry( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) + { + var sb = new StringBuilder(); + + sb.Append($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}]"); + sb.Append($" [{GetLogLevelShort(logLevel)}]"); + sb.Append($" [{_categoryName}]"); + + if (eventId.Id != 0 || !string.IsNullOrEmpty(eventId.Name)) + sb.Append($" [{eventId}]"); + + sb.Append($" {formatter(state, exception)}"); + + if (exception != null) + AppendException(sb, exception); + + return sb.ToString(); + } + + private static void AppendException(StringBuilder sb, Exception exception, int depth = 0) + { + while (true) + { + var indent = depth == 0 ? "" : " Inner "; + sb.AppendLine(); + sb.Append($"{indent}Exception: {exception.GetType().FullName}: {exception.Message}"); + if (!string.IsNullOrWhiteSpace(exception.StackTrace)) + { + sb.AppendLine(); + sb.Append($"{indent}StackTrace: {exception.StackTrace.Trim()}"); + } + + if (exception.InnerException != null) + { + exception = exception.InnerException; + depth += 1; + continue; + } + + break; + } + } + + private static string GetLogLevelShort(LogLevel level) => level switch + { + LogLevel.Trace => "TRC", + LogLevel.Debug => "DBG", + LogLevel.Information => "INF", + LogLevel.Warning => "WRN", + LogLevel.Error => "ERR", + LogLevel.Critical => "CRT", + _ => "???" + }; +} \ No newline at end of file diff --git a/SteUp.Maui/Core/Logger/FileLoggerProvider.cs b/SteUp.Maui/Core/Logger/FileLoggerProvider.cs new file mode 100644 index 0000000..86abfd6 --- /dev/null +++ b/SteUp.Maui/Core/Logger/FileLoggerProvider.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Logging; + +namespace SteUp.Maui.Core.Logger; + +public class FileLoggerProvider(string path, string logFilePrefix, int retentionDays = 60) + : ILoggerProvider +{ + public ILogger CreateLogger(string categoryName) + { + return new FileLogger(path, logFilePrefix, categoryName, retentionDays); + } + + public void Dispose() { } +} diff --git a/SteUp.Maui/Core/Services/AttachedService.cs b/SteUp.Maui/Core/Services/AttachedService.cs index 37875e5..5ceedc3 100644 --- a/SteUp.Maui/Core/Services/AttachedService.cs +++ b/SteUp.Maui/Core/Services/AttachedService.cs @@ -1,15 +1,13 @@ -using SteUp.Shared.Core.Dto; -using SteUp.Shared.Core.Entities; +using Microsoft.Extensions.Logging; +using SteUp.Maui.Core.Utility; +using SteUp.Shared.Core.Dto; using SteUp.Shared.Core.Helpers; using SteUp.Shared.Core.Interface.System; namespace SteUp.Maui.Core.Services; -public class AttachedService : IAttachedService +public class AttachedService(ILogger logger) : IAttachedService { - private static string AttachedRoot => - Path.Combine(FileSystem.CacheDirectory, "attached"); - public async Task SelectImageFromCamera() { var cameraPerm = await Permissions.RequestAsync(); @@ -27,12 +25,13 @@ public class AttachedService : IAttachedService } catch (Exception ex) { + logger.LogError(ex, ex.Message); Console.WriteLine($"Errore cattura foto: {ex.Message}"); SentrySdk.CaptureException(ex); return null; } - return result is null ? null : await ConvertToDto(result, AttachedDto.TypeAttached.Image); + return result is null ? null : await UtilityFile.ConvertToDto(result, AttachedDto.TypeAttached.Image); } public async Task?> SelectImageFromGallery() @@ -48,6 +47,7 @@ public class AttachedService : IAttachedService } catch (Exception ex) { + logger.LogError(ex, ex.Message); Console.WriteLine($"Errore selezione galleria: {ex.Message}"); SentrySdk.CaptureException(ex); return null; @@ -58,308 +58,9 @@ public class AttachedService : IAttachedService List returnList = []; foreach (var fileResult in resultList) { - returnList.Add(await ConvertToDto(fileResult, AttachedDto.TypeAttached.Image)); + returnList.Add(await UtilityFile.ConvertToDto(fileResult, AttachedDto.TypeAttached.Image)); } return returnList; } - - private static async Task ConvertToDto(FileResult file, AttachedDto.TypeAttached type) - { - var stream = await file.OpenReadAsync(); - using var ms = new MemoryStream(); - await stream.CopyToAsync(ms); - - return new AttachedDto - { - Name = file.FileName, - Path = file.FullPath, - MimeType = file.ContentType, - DimensionBytes = ms.Length, - FileBytes = ms.ToArray(), - Type = type - }; - } - - private async Task ConvertToDto(FileInfo file, AttachedDto.TypeAttached type, bool isFromToUpload) - { - var (origUrl, thumbUrl) = await SaveAndCreateThumbAsync( - await File.ReadAllBytesAsync(file.FullName), - file.Name - ); - - return new AttachedDto - { - Name = file.Name, - Path = file.FullName, - TempPath = origUrl, - ThumbPath = thumbUrl, - Type = type, - SavedOnAppData = true, - ToUpload = isFromToUpload - }; - } - - private const string ToUploadFolderName = "toUpload"; - - private string GetInspectionBaseDir(Ispezione ispezione) - { - var baseDir = FileSystem.AppDataDirectory; - return Path.Combine(baseDir, $"attached_{GetInspectionKey(ispezione)}"); - } - - private string GetInspectionToUploadDir(Ispezione ispezione) - => Path.Combine(GetInspectionBaseDir(ispezione), ToUploadFolderName); - - private string GetInspectionFinalDir(Ispezione ispezione) - => GetInspectionBaseDir(ispezione); - - /// - /// Ritorna i file dell'ispezione filtrati per nome. - /// Per default include sia "final" sia "toUpload" (utile per UI). - /// - public async Task?> GetInspectionFiles( - Ispezione ispezione, - List fileNameFilter, - bool includeToUpload, - CancellationToken ct) - { - ArgumentNullException.ThrowIfNull(ispezione); - ArgumentNullException.ThrowIfNull(fileNameFilter); - - var baseDir = GetInspectionBaseDir(ispezione); - if (!Directory.Exists(baseDir)) return null; - - var result = new List(); - - var finalDir = GetInspectionFinalDir(ispezione); - if (Directory.Exists(finalDir)) - { - var finalFiles = new DirectoryInfo(finalDir) - .GetFiles("*", SearchOption.TopDirectoryOnly); - - foreach (var file in finalFiles) - { - if (file.Directory?.Name == ToUploadFolderName) - continue; - - if (!fileNameFilter.Contains(file.Name)) - continue; - - ct.ThrowIfCancellationRequested(); - - result.Add(await ConvertToDto( - file, - AttachedDto.TypeAttached.Image, - isFromToUpload: false)); - } - } - - if (!includeToUpload) return result; - - var toUploadDir = GetInspectionToUploadDir(ispezione); - if (!Directory.Exists(toUploadDir)) return result; - - var toUploadFiles = new DirectoryInfo(toUploadDir) - .GetFiles("*", SearchOption.TopDirectoryOnly); - - foreach (var file in toUploadFiles) - { - if (!fileNameFilter.Contains(file.Name)) - continue; - - ct.ThrowIfCancellationRequested(); - - result.Add(await ConvertToDto( - file, - AttachedDto.TypeAttached.Image, - isFromToUpload: true)); - } - - return result; - } - - /// - /// Salva SEMPRE in /toUpload. - /// - public async Task SaveInspectionFile( - Ispezione ispezione, - byte[] file, - string fileName, - CancellationToken ct) - { - ArgumentNullException.ThrowIfNull(ispezione); - ArgumentNullException.ThrowIfNull(file); - ArgumentException.ThrowIfNullOrWhiteSpace(fileName); - - var toUploadDir = GetInspectionToUploadDir(ispezione); - Directory.CreateDirectory(toUploadDir); - - var filePath = Path.Combine(toUploadDir, fileName); - await File.WriteAllBytesAsync(filePath, file, ct); - - return filePath; - } - - public Task MoveInspectionFileFromToUploadToFinal( - Ispezione ispezione, - string fileName, - bool overwrite, - CancellationToken ct) - { - ArgumentNullException.ThrowIfNull(ispezione); - ArgumentException.ThrowIfNullOrWhiteSpace(fileName); - - ct.ThrowIfCancellationRequested(); - - var toUploadDir = GetInspectionToUploadDir(ispezione); - var finalDir = GetInspectionFinalDir(ispezione); - - if (!Directory.Exists(toUploadDir)) return Task.FromResult(false); - - var sourcePath = Path.Combine(toUploadDir, fileName); - if (!File.Exists(sourcePath)) return Task.FromResult(false); - - Directory.CreateDirectory(finalDir); - - var destPath = Path.Combine(finalDir, fileName); - - if (File.Exists(destPath)) - { - if (!overwrite) return Task.FromResult(false); - File.Delete(destPath); - } - - File.Move(sourcePath, destPath); - - // Pulizia: se /toUpload resta vuota la elimino - CleanupDirectoriesIfEmpty(ispezione); - - return Task.FromResult(true); - } - - /// - /// Rimuove un file cercandolo prima in /toUpload e poi in final (o viceversa). - /// Default: prova a cancellare ovunque. - /// - public bool RemoveInspectionFile( - Ispezione ispezione, - string fileName, - bool removeAlsoFromFinal, - bool removeAlsoFromToUpload) - { - ArgumentNullException.ThrowIfNull(ispezione); - if (string.IsNullOrWhiteSpace(fileName)) return false; - - var removed = false; - - if (removeAlsoFromToUpload) - { - var toUploadPath = Path.Combine(GetInspectionToUploadDir(ispezione), fileName); - if (File.Exists(toUploadPath)) - { - File.Delete(toUploadPath); - removed = true; - } - } - - if (removeAlsoFromFinal) - { - var finalPath = Path.Combine(GetInspectionFinalDir(ispezione), fileName); - if (File.Exists(finalPath)) - { - File.Delete(finalPath); - removed = true; - } - } - - if (removed) - CleanupDirectoriesIfEmpty(ispezione); - - return removed; - } - - private void CleanupDirectoriesIfEmpty(Ispezione ispezione) - { - var baseDir = GetInspectionBaseDir(ispezione); - var toUploadDir = GetInspectionToUploadDir(ispezione); - - // 1) se /toUpload esiste e vuota => delete - if (Directory.Exists(toUploadDir) && !Directory.EnumerateFileSystemEntries(toUploadDir).Any()) - Directory.Delete(toUploadDir); - - // 2) se base dir vuota (attenzione: dopo delete toUpload) => delete - if (Directory.Exists(baseDir) && !Directory.EnumerateFileSystemEntries(baseDir).Any()) - Directory.Delete(baseDir); - } - - public async Task SaveToTempStorage(Stream file, string fileName, CancellationToken ct = default) - { - ArgumentNullException.ThrowIfNull(file); - - if (file.CanSeek) - file.Position = 0; - - fileName = Path.GetFileName(fileName); - - var dir = FileSystem.CacheDirectory; - var filePath = Path.Combine(dir, fileName); - - await using var fileStream = File.Create(filePath); - await file.CopyToAsync(fileStream, ct); - - return filePath; - } - - public Task CleanTempStorageAsync(CancellationToken ct = default) - { - return Task.Run(() => - { - if (Directory.Exists(AttachedRoot)) - Directory.Delete(AttachedRoot, true); - }, ct); - } - - public Task OpenFile(string fileName, string filePath) - { -#if IOS - throw new NotImplementedException(); -#else - return Launcher.OpenAsync(new OpenFileRequest - { - Title = "Apri file", - File = new ReadOnlyFile(filePath) - }); -#endif - } - - public async Task<(string originalUrl, string thumbUrl)> SaveAndCreateThumbAsync( - byte[] bytes, string fileName, CancellationToken ct = default) - { - Directory.CreateDirectory(AttachedRoot); - - var id = Guid.NewGuid().ToString("N"); - var safeName = SanitizeFileName(fileName); - - var originalFile = $"{id}_{safeName}"; - var thumbFile = $"{id}_thumb.jpg"; - - var originalPath = Path.Combine(AttachedRoot, originalFile); - await File.WriteAllBytesAsync(originalPath, bytes, ct); - - var thumbPath = Path.Combine(AttachedRoot, thumbFile); - await ImageThumb.CreateThumbnailAsync(originalPath, thumbPath, maxSide: 320, quality: 70, ct); - - return ($"https://localfiles/attached/{originalFile}", - $"https://localfiles/attached/{thumbFile}"); - } - - private static string SanitizeFileName(string fileName) - { - var name = Path.GetFileName(fileName); - return Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, '_')); - } - - private static string GetInspectionKey(Ispezione ispezione) => - $"{ispezione.CodMdep}_{ispezione.Data:ddMMyyyy}_{ispezione.Rilevatore.ToLower()}"; } \ No newline at end of file diff --git a/SteUp.Maui/Core/Services/FileManager.cs b/SteUp.Maui/Core/Services/FileManager.cs new file mode 100644 index 0000000..26ef2e6 --- /dev/null +++ b/SteUp.Maui/Core/Services/FileManager.cs @@ -0,0 +1,442 @@ +using System.IO.Compression; +using SteUp.Data.LocalDb; +using SteUp.Shared.Core.Dto; +using SteUp.Shared.Core.Entities; +using SteUp.Shared.Core.Helpers; +using SteUp.Shared.Core.Interface.System; + +namespace SteUp.Maui.Core.Services; + +public class FileManager(IDbPathProvider dbPathProvider) : IFileManager +{ + private static string AttachedRoot => + Path.Combine(FileSystem.CacheDirectory, "attached"); + + private async Task ConvertToDto(FileInfo file, AttachedDto.TypeAttached type, bool isFromToUpload) + { + var (origUrl, thumbUrl) = await SaveAndCreateThumbAsync( + await File.ReadAllBytesAsync(file.FullName), + file.Name + ); + + return new AttachedDto + { + Name = file.Name, + Path = file.FullName, + TempPath = origUrl, + ThumbPath = thumbUrl, + Type = type, + SavedOnAppData = true, + ToUpload = isFromToUpload + }; + } + + private const string ToUploadFolderName = "toUpload"; + + private string GetInspectionBaseDir(Ispezione ispezione) + { + var baseDir = FileSystem.AppDataDirectory; + return Path.Combine(baseDir, "attached", $"inspection_{GetInspectionKey(ispezione)}"); + } + + private string GetInspectionToUploadDir(Ispezione ispezione) + => Path.Combine(GetInspectionBaseDir(ispezione), ToUploadFolderName); + + public string GetFileToUploadDir(Ispezione ispezione, string fileName) => + Path.Combine(GetInspectionToUploadDir(ispezione), fileName); + + private string GetInspectionFinalDir(Ispezione ispezione) + => GetInspectionBaseDir(ispezione); + + /// + /// Ritorna i file dell'ispezione filtrati per nome. + /// Per default include sia "final" sia "toUpload" (utile per UI). + /// + public async Task?> GetInspectionFiles( + Ispezione ispezione, + List fileNameFilter, + bool includeToUpload, + CancellationToken ct) + { + ArgumentNullException.ThrowIfNull(ispezione); + ArgumentNullException.ThrowIfNull(fileNameFilter); + + var baseDir = GetInspectionBaseDir(ispezione); + if (!Directory.Exists(baseDir)) return null; + + var result = new List(); + + var finalDir = GetInspectionFinalDir(ispezione); + if (Directory.Exists(finalDir)) + { + var finalFiles = new DirectoryInfo(finalDir) + .GetFiles("*", SearchOption.TopDirectoryOnly); + + foreach (var file in finalFiles) + { + if (file.Directory?.Name == ToUploadFolderName) + continue; + + if (!fileNameFilter.Contains(file.FullName)) + continue; + + ct.ThrowIfCancellationRequested(); + + result.Add(await ConvertToDto( + file, + AttachedDto.TypeAttached.Image, + isFromToUpload: false)); + } + } + + if (!includeToUpload) return result; + + var toUploadDir = GetInspectionToUploadDir(ispezione); + if (!Directory.Exists(toUploadDir)) return result; + + var toUploadFiles = new DirectoryInfo(toUploadDir) + .GetFiles("*", SearchOption.TopDirectoryOnly); + + foreach (var file in toUploadFiles) + { + if (!fileNameFilter.Contains(file.Name)) + continue; + + ct.ThrowIfCancellationRequested(); + + result.Add(await ConvertToDto( + file, + AttachedDto.TypeAttached.Image, + isFromToUpload: true)); + } + + return result; + } + + /// + /// Salva SEMPRE in /toUpload. + /// + public async Task SaveInspectionFile( + Ispezione ispezione, + byte[] file, + string fileName, + CancellationToken ct) + { + ArgumentNullException.ThrowIfNull(ispezione); + ArgumentNullException.ThrowIfNull(file); + ArgumentException.ThrowIfNullOrWhiteSpace(fileName); + + var toUploadDir = GetInspectionToUploadDir(ispezione); + Directory.CreateDirectory(toUploadDir); + + var filePath = Path.Combine(toUploadDir, fileName); + await File.WriteAllBytesAsync(filePath, file, ct); + + return filePath; + } + + public Task MoveInspectionFileFromToUploadToFinal( + Ispezione ispezione, + string fileName, + bool overwrite, + CancellationToken ct) + { + ArgumentNullException.ThrowIfNull(ispezione); + ArgumentException.ThrowIfNullOrWhiteSpace(fileName); + + ct.ThrowIfCancellationRequested(); + + var toUploadDir = GetInspectionToUploadDir(ispezione); + var finalDir = GetInspectionFinalDir(ispezione); + + if (!Directory.Exists(toUploadDir)) return Task.FromResult(null); + + var sourcePath = Path.Combine(toUploadDir, fileName); + if (!File.Exists(sourcePath)) return Task.FromResult(null); + + Directory.CreateDirectory(finalDir); + + var destPath = Path.Combine(finalDir, fileName); + + if (File.Exists(destPath)) + { + if (!overwrite) return Task.FromResult(null); + File.Delete(destPath); + } + + File.Move(sourcePath, destPath); + + // Pulizia: se /toUpload resta vuota la elimino + CleanupDirectoriesIfEmpty(ispezione); + + return Task.FromResult(destPath); + } + + /// + /// Rimuove un file cercandolo prima in /toUpload e poi in final (o viceversa). + /// Default: prova a cancellare ovunque. + /// + public bool RemoveInspectionFile( + Ispezione ispezione, + string fileName, + bool removeAlsoFromFinal, + bool removeAlsoFromToUpload) + { + ArgumentNullException.ThrowIfNull(ispezione); + if (string.IsNullOrWhiteSpace(fileName)) return false; + + var removed = false; + + if (removeAlsoFromToUpload) + { + var toUploadPath = Path.Combine(GetInspectionToUploadDir(ispezione), fileName); + if (File.Exists(toUploadPath)) + { + File.Delete(toUploadPath); + removed = true; + } + } + + if (removeAlsoFromFinal) + { + var finalPath = Path.Combine(GetInspectionFinalDir(ispezione), fileName); + if (File.Exists(finalPath)) + { + File.Delete(finalPath); + removed = true; + } + } + + if (removed) + CleanupDirectoriesIfEmpty(ispezione); + + return removed; + } + + private void CleanupDirectoriesIfEmpty(Ispezione ispezione) + { + var baseDir = GetInspectionBaseDir(ispezione); + var toUploadDir = GetInspectionToUploadDir(ispezione); + + // 1) se /toUpload esiste e vuota => delete + if (Directory.Exists(toUploadDir) && !Directory.EnumerateFileSystemEntries(toUploadDir).Any()) + Directory.Delete(toUploadDir); + + // 2) se base dir vuota (attenzione: dopo delete toUpload) => delete + if (Directory.Exists(baseDir) && !Directory.EnumerateFileSystemEntries(baseDir).Any()) + Directory.Delete(baseDir); + } + + public async Task SaveToTempStorage(Stream file, string fileName, CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(file); + + if (file.CanSeek) + file.Position = 0; + + fileName = Path.GetFileName(fileName); + + var dir = FileSystem.CacheDirectory; + var filePath = Path.Combine(dir, fileName); + + await using var fileStream = File.Create(filePath); + await file.CopyToAsync(fileStream, ct); + + return filePath; + } + + public Task CleanTempStorageAsync(CancellationToken ct = default) + { + return Task.Run(() => + { + if (Directory.Exists(AttachedRoot)) + Directory.Delete(AttachedRoot, true); + }, ct); + } + + public Task OpenFile(string fileName, string filePath) + { +#if IOS + throw new NotImplementedException(); +#else + return Launcher.OpenAsync(new OpenFileRequest + { + Title = "Apri file", + File = new ReadOnlyFile(filePath) + }); +#endif + } + + public async Task<(string originalUrl, string thumbUrl)> SaveAndCreateThumbAsync( + byte[] bytes, string fileName, CancellationToken ct = default) + { + Directory.CreateDirectory(AttachedRoot); + + var id = Guid.NewGuid().ToString("N"); + var safeName = SanitizeFileName(fileName); + + var originalFile = $"{id}_{safeName}"; + var thumbFile = $"{id}_thumb.jpg"; + + var originalPath = Path.Combine(AttachedRoot, originalFile); + await File.WriteAllBytesAsync(originalPath, bytes, ct); + + var thumbPath = Path.Combine(AttachedRoot, thumbFile); + await ImageThumb.CreateThumbnailAsync(originalPath, thumbPath, maxSide: 320, quality: 70, ct); + + return ($"https://localfiles/attached/{originalFile}", + $"https://localfiles/attached/{thumbFile}"); + } + + private static string SanitizeFileName(string fileName) + { + var name = Path.GetFileName(fileName); + return Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, '_')); + } + + private static string GetInspectionKey(Ispezione ispezione) => + $"{ispezione.CodMdep}_{ispezione.Data:ddMMyyyy}_{ispezione.Rilevatore.ToLower()}"; + + public List GetFileForExport() + { + var attachments = new List(); + + // 1) log file singolo (se ti serve ancora) + var logFile = RetrieveLogFile(); + if (!logFile.IsNullOrEmpty()) + { + attachments.Add(new SendEmailDto.AttachmentsDto + { + FileName = $"logs_{DateTime.Today:yyyyMMdd}.zip", + FileContent = CreateZipBytes(logFile!) + }); + } + + // 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 + .EnumerateFiles("*", SearchOption.AllDirectories) + .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? RetrieveLogFile() + { + var appDataPath = FileSystem.AppDataDirectory; + var targetDirectory = Path.Combine(appDataPath, "logs"); + + var directory = new DirectoryInfo(targetDirectory); + + List? 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 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 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(); + } +} \ No newline at end of file diff --git a/SteUp.Maui/Core/Utility/UtilityFile.cs b/SteUp.Maui/Core/Utility/UtilityFile.cs new file mode 100644 index 0000000..83665e7 --- /dev/null +++ b/SteUp.Maui/Core/Utility/UtilityFile.cs @@ -0,0 +1,23 @@ +using SteUp.Shared.Core.Dto; + +namespace SteUp.Maui.Core.Utility; + +public static class UtilityFile +{ + public static async Task ConvertToDto(FileResult file, AttachedDto.TypeAttached type) + { + var stream = await file.OpenReadAsync(); + using var ms = new MemoryStream(); + await stream.CopyToAsync(ms); + + return new AttachedDto + { + Name = file.FileName, + Path = file.FullPath, + MimeType = file.ContentType, + DimensionBytes = ms.Length, + FileBytes = ms.ToArray(), + Type = type + }; + } +} \ No newline at end of file diff --git a/SteUp.Maui/Core/UtilityException/GlobalExceptionHandler.cs b/SteUp.Maui/Core/UtilityException/GlobalExceptionHandler.cs new file mode 100644 index 0000000..f828cf5 --- /dev/null +++ b/SteUp.Maui/Core/UtilityException/GlobalExceptionHandler.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Logging; + +namespace SteUp.Maui.Core.UtilityException; + +public static class GlobalExceptionHandler +{ + public static void Register(ILogger logger) + { + AppDomain.CurrentDomain.UnhandledException += (_, args) => + { + var ex = args.ExceptionObject as Exception; + logger.LogCritical(ex, "UnhandledException (AppDomain) — IsTerminating: {t}", args.IsTerminating); + }; + + TaskScheduler.UnobservedTaskException += (_, args) => + { + logger.LogCritical(args.Exception, "UnobservedTaskException"); + args.SetObserved(); + }; + +#if ANDROID + Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += (_, args) => + { + logger.LogCritical(args.Exception, "Android UnhandledException"); + args.Handled = true; + }; +#endif + +#if IOS || MACCATALYST + ObjCRuntime.Runtime.MarshalManagedException += (_, args) => + { + logger.LogCritical(args.Exception, "iOS MarshalManagedException"); + args.ExceptionMode = ObjCRuntime.MarshalManagedExceptionMode.UnwindNativeCode; + }; +#endif + } +} \ No newline at end of file diff --git a/SteUp.Maui/MauiProgram.cs b/SteUp.Maui/MauiProgram.cs index 8c7162b..39efb07 100644 --- a/SteUp.Maui/MauiProgram.cs +++ b/SteUp.Maui/MauiProgram.cs @@ -46,6 +46,7 @@ namespace SteUp.Maui builder.RegisterSystemService(); builder.RegisterDbServices(); builder.RegisterMessageServices(); + builder.RegisterLoggerServices(); return builder.Build(); } diff --git a/SteUp.Shared/Components/Pages/IspezionePage.razor b/SteUp.Shared/Components/Pages/IspezionePage.razor index 9df680e..4a688e0 100644 --- a/SteUp.Shared/Components/Pages/IspezionePage.razor +++ b/SteUp.Shared/Components/Pages/IspezionePage.razor @@ -1,4 +1,5 @@ @page "/ispezione" +@using Microsoft.Extensions.Logging @using SteUp.Shared.Components.Layout @using SteUp.Shared.Components.Layout.Overlay @using SteUp.Shared.Components.SingleElements.Card @@ -15,7 +16,8 @@ @inject IIspezioniService IspezioniService @inject IDialogService Dialog @inject IIntegrySteupService IntegrySteupService -@inject IAttachedService AttachedService +@inject IFileManager FileManager +@inject ILogger Logger @implements IDisposable @@ -150,7 +152,7 @@ if (scheda.ImageNames == null) continue; - var fileList = (await AttachedService.GetInspectionFiles(ispezione, scheda.ImageNames))? + var fileList = (await FileManager.GetInspectionFiles(ispezione, scheda.ImageNames))? .Where(x => x.ToUpload).ToList(); if (fileList == null) continue; @@ -187,7 +189,7 @@ StateHasChanged(); }); - OnError(e.Message); + OnError(e, e.Message); } } @@ -286,8 +288,9 @@ StateHasChanged(); } - private void OnError(string? errorMessage) + private void OnError(Exception? e, string? errorMessage) { + if (e != null) Logger.LogError(e, errorMessage); if (errorMessage == null) return; _ = Dialog.ShowError(errorMessage); diff --git a/SteUp.Shared/Components/Pages/LoginPage.razor b/SteUp.Shared/Components/Pages/LoginPage.razor index b20601f..b325120 100644 --- a/SteUp.Shared/Components/Pages/LoginPage.razor +++ b/SteUp.Shared/Components/Pages/LoginPage.razor @@ -1,4 +1,5 @@ @page "/login" +@using Microsoft.Extensions.Logging @using SteUp.Shared.Components.Layout.Spinner @using SteUp.Shared.Core.BarcodeReader.Contracts @using SteUp.Shared.Core.Interface.System @@ -7,6 +8,7 @@ @inject AppAuthenticationStateProvider AuthenticationStateProvider @inject IGenericSystemService GenericSystemService @inject IBarcodeManager BarcodeManager +@inject ILogger Logger @if (Spinner) { @@ -100,12 +102,13 @@ else } catch (Exception e) { + ErrorMessage = e.Message; + Logger.LogError(e, ErrorMessage); Console.WriteLine(e.Message); Spinner = false; StateHasChanged(); - - ErrorMessage = e.Message; + _attemptFailed = true; Console.WriteLine(e); } diff --git a/SteUp.Shared/Components/Pages/UserPage.razor b/SteUp.Shared/Components/Pages/UserPage.razor index 774c67c..3c06a23 100644 --- a/SteUp.Shared/Components/Pages/UserPage.razor +++ b/SteUp.Shared/Components/Pages/UserPage.razor @@ -1,13 +1,19 @@ @page "/user" @attribute [Authorize] @using SteUp.Shared.Components.Layout +@using SteUp.Shared.Components.Layout.Overlay @using SteUp.Shared.Components.SingleElements @using SteUp.Shared.Core.Authorization.Enum -@using SteUp.Shared.Core.Interface.System.Network +@using SteUp.Shared.Core.Dto +@using SteUp.Shared.Core.Interface.IntegryApi +@using SteUp.Shared.Core.Interface.System @using SteUp.Shared.Core.Services @using SteUp.Shared.Core.Utility @inject AppAuthenticationStateProvider AuthenticationStateProvider @inject INetworkService NetworkService +@inject IGenericSystemService GenericSystemService +@inject IFileManager FileManager +@inject IIntegryApiService IntegryApiService @@ -62,19 +68,30 @@ + +
+ + Esporta log + +
-
+
- Sincronizza ispezioni + Sincronizza ispezioni esportate
-
+
} + + @code { private bool IsLoggedIn { get; set; } + private bool VisibleOverlay { get; set; } private string? CodHash { get; set; } = ""; protected override async Task OnInitializedAsync() @@ -100,8 +120,41 @@ StateHasChanged(); } - private void UpdateDb() + private async Task ExportLog() { + VisibleOverlay = true; + StateHasChanged(); + + var profiloAzienda = LocalStorage.GetString("codHash"); + + var email = new SendEmailDto + { + FromName = "Integry Log", + To = "developer@integry.it", + Subject = $"SteUP - Log del {DateTime.Today:d} di {UserSession.User.Username}", + IsHtml = true, + MsgText = $"Username: {UserSession.User.Username}
" + + $"Profilo azienda: {profiloAzienda}
" + + $"ProfileDb: {UserSession.ProfileDb}
" + + $"Versione app: {GenericSystemService.GetCurrentAppVersion()}", + Attachments = FileManager.GetFileForExport() + }; + + await IntegryApiService.SendEmail(email); + + VisibleOverlay = false; + StateHasChanged(); + } + + private async Task UpdateDb() + { + VisibleOverlay = true; + StateHasChanged(); + + await SteupDataService.CheckAndUpdateStatus(); + + VisibleOverlay = false; + StateHasChanged(); } private async Task Logout() diff --git a/SteUp.Shared/Components/SingleElements/Card/SchedaCard.razor b/SteUp.Shared/Components/SingleElements/Card/SchedaCard.razor index c20ffc1..8523673 100644 --- a/SteUp.Shared/Components/SingleElements/Card/SchedaCard.razor +++ b/SteUp.Shared/Components/SingleElements/Card/SchedaCard.razor @@ -9,7 +9,7 @@ @inject IIspezioniService IspezioniService @inject IIntegrySteupService IntegrySteupService @inject IDialogService Dialog -@inject IAttachedService AttachedService +@inject IFileManager FileManager
@@ -104,7 +104,7 @@ { foreach (var fileName in Scheda.ImageNames) { - AttachedService.RemoveInspectionFile(Ispezione, fileName); + FileManager.RemoveInspectionFile(Ispezione, fileName); } } @@ -159,7 +159,7 @@ { if (Scheda.ImageNames == null) return; - var fileList = (await AttachedService.GetInspectionFiles(Ispezione, Scheda.ImageNames))? + var fileList = (await FileManager.GetInspectionFiles(Ispezione, Scheda.ImageNames))? .Where(x => x.ToUpload).ToList(); if (fileList == null) return; diff --git a/SteUp.Shared/Components/SingleElements/Modal/ExceptionModal/ExceptionModal.razor b/SteUp.Shared/Components/SingleElements/Modal/ExceptionModal/ExceptionModal.razor index 19fa4f4..7580d76 100644 --- a/SteUp.Shared/Components/SingleElements/Modal/ExceptionModal/ExceptionModal.razor +++ b/SteUp.Shared/Components/SingleElements/Modal/ExceptionModal/ExceptionModal.razor @@ -1,4 +1,7 @@ -
+@using Microsoft.Extensions.Logging +@inject ILogger Logger + +
@@ -43,6 +46,8 @@ { Message = Exception.Message; } + + Logger.LogError(Exception, "Errore nel componente Blazor: {Message}", Message); StateHasChanged(); } diff --git a/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor b/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor index 12ef106..6654ac4 100644 --- a/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor +++ b/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor @@ -1,4 +1,6 @@ -@using SteUp.Shared.Components.Layout +@using System.Data.Common +@using Microsoft.Extensions.Logging +@using SteUp.Shared.Components.Layout @using SteUp.Shared.Components.Layout.Overlay @using SteUp.Shared.Components.Layout.Spinner @using SteUp.Shared.Components.SingleElements.Card.ModalForm @@ -13,10 +15,11 @@ @inject INetworkService NetworkService @inject IDialogService Dialog @inject IIntegryApiService IntegryApiService -@inject IAttachedService AttachedService +@inject IFileManager FileManager @inject IIspezioniService IspezioniService @inject IIntegrySteupService IntegrySteupService @inject OnScannerService OnScannerService +@inject ILogger Logger @@ -75,7 +78,7 @@ } @item.p.Name - @if (IsNew) + @if (item.p.ToUpload) { { - var fileList = await AttachedService.GetInspectionFiles( + var fileList = await FileManager.GetInspectionFiles( new Ispezione { CodMdep = CodMdep, @@ -285,17 +288,19 @@ } catch (Exception e) { - Console.WriteLine(e.Message); + var message = e.Message; - await Dialog.ShowError(e.Message); + Logger.LogError(e, message); + Console.WriteLine(message); + await Dialog.ShowError(message); } if (IsNew) await NewSave(apiResponse); else await Update(apiResponse); - if (Scheda.ActivityId.IsValorized()) await UploadFile(Scheda.ActivityId!); + if (Scheda.ActivityId.IsValorized()) await UploadFile(Scheda); - await AttachedService.CleanTempStorageAsync(); + await FileManager.CleanTempStorageAsync(); SuccessAnimation = true; StateHasChanged(); @@ -311,7 +316,7 @@ { foreach (var attached in AttachedList!) { - var fileNameAdded = await AttachedService.SaveInspectionFile( + var fileNameAdded = await FileManager.SaveInspectionFile( new Ispezione { CodMdep = CodMdep, @@ -354,7 +359,7 @@ if (!attached.ToRemove) { - var fileNameAdded = await AttachedService.SaveInspectionFile( + var fileNameAdded = await FileManager.SaveInspectionFile( ispezione, attached.FileBytes!, attached.Name! @@ -365,7 +370,7 @@ Scheda.ImageNames.Add(fileNameAdded); } else - _ = AttachedService.RemoveInspectionFile(ispezione, attached.Name!); + _ = FileManager.RemoveInspectionFile(ispezione, attached.Name!); } } @@ -378,7 +383,7 @@ ); } - private async Task UploadFile(string activityId) + private async Task UploadFile(Scheda scheda) { if (AttachedList.IsNullOrEmpty()) return; @@ -393,16 +398,23 @@ { if (file.FileBytes == null || file.Name == null) continue; - await IntegrySteupService.UploadFile(activityId, file.FileBytes, file.Name); - await AttachedService.MoveInspectionFileFromToUploadToFinal(ispezione, file.Name); + await IntegrySteupService.UploadFile(scheda.ActivityId!, file.FileBytes, file.Name); + var newPath = await FileManager.MoveInspectionFileFromToUploadToFinal(ispezione, file.Name); + if (newPath == null) continue; + + var filePathToRemove = FileManager.GetFileToUploadDir(ispezione, file.Name); + scheda.ImageNames!.Remove(filePathToRemove); + scheda.ImageNames.Add(newPath); } + + await IspezioniService.UpdateFileListSchedaAsync(scheda.Id, scheda.ImageNames); } private async Task Cancel() { if (await CheckSavePreAction()) { - await AttachedService.CleanTempStorageAsync(); + await FileManager.CleanTempStorageAsync(); DisposeMessage(); MudDialog.Cancel(); } @@ -500,7 +512,7 @@ var a = attachedList[i]; if (a.FileBytes is null || a.Name is null) continue; - var (origUrl, thumbUrl) = await AttachedService.SaveAndCreateThumbAsync(a.FileBytes, a.Name); + var (origUrl, thumbUrl) = await FileManager.SaveAndCreateThumbAsync(a.FileBytes, a.Name); await InvokeAsync(() => { @@ -557,7 +569,7 @@ TextLoading = null; StateHasChanged(); - OnError(e.Message); + OnError(e, e.Message); }); return; @@ -670,7 +682,7 @@ } else { - OnError("Nessun articolo trovato"); + OnError(null, "Nessun articolo trovato"); } }); } @@ -684,18 +696,19 @@ StateHasChanged(); }); - OnError(e.Message); + OnError(e, e.Message); } } - private void OnErrorScan(string? value) => OnError(value); + private void OnErrorScan(string? value) => OnError(new Exception(value), value); #endregion - private void OnError(string? errorMessage) + private void OnError(Exception? e, string? errorMessage) { + if (e != null) Logger.LogError(e, errorMessage); if (errorMessage == null) return; - + _ = Dialog.ShowError(errorMessage); } diff --git a/SteUp.Shared/Components/SingleElements/Modal/ModalSelectShop.razor b/SteUp.Shared/Components/SingleElements/Modal/ModalSelectShop.razor index 1c403ff..b3412bd 100644 --- a/SteUp.Shared/Components/SingleElements/Modal/ModalSelectShop.razor +++ b/SteUp.Shared/Components/SingleElements/Modal/ModalSelectShop.razor @@ -8,7 +8,7 @@
Seleziona il negozio - +
@@ -67,19 +67,20 @@ if (FilterText.IsNullOrEmpty()) { FilteredList = SteupDataService.PuntiVenditaList; - StateHasChanged(); - return; + } + else + { + FilteredList = SteupDataService.PuntiVenditaList.FindAll(x => + (x.Indirizzo != null && x.Indirizzo.ContainsIgnoreCase(FilterText!)) || + (x.Descrizione != null && x.Descrizione.ContainsIgnoreCase(FilterText!)) || + (x.CodMdep != null && x.CodMdep.ContainsIgnoreCase(FilterText!)) || + (x.Citta != null && x.Citta.ContainsIgnoreCase(FilterText!)) || + (x.Cap != null && x.Cap.ContainsIgnoreCase(FilterText!)) || + (x.Provincia != null && x.Provincia.ContainsIgnoreCase(FilterText!)) + ); } - FilteredList = SteupDataService.PuntiVenditaList.FindAll(x => - (x.Indirizzo != null && x.Indirizzo.ContainsIgnoreCase(FilterText!)) || - (x.Descrizione != null && x.Descrizione.ContainsIgnoreCase(FilterText!)) || - (x.CodMdep != null && x.CodMdep.ContainsIgnoreCase(FilterText!)) || - (x.Citta != null && x.Citta.ContainsIgnoreCase(FilterText!)) || - (x.Cap != null && x.Cap.ContainsIgnoreCase(FilterText!)) || - (x.Provincia != null && x.Provincia.ContainsIgnoreCase(FilterText!)) - ); - + FilteredList = FilteredList.OrderBy(x => x.CodMdep).ToList(); StateHasChanged(); } diff --git a/SteUp.Shared/Core/Data/Contracts/ISteupDataService.cs b/SteUp.Shared/Core/Data/Contracts/ISteupDataService.cs index 45b668c..677b5c6 100644 --- a/SteUp.Shared/Core/Data/Contracts/ISteupDataService.cs +++ b/SteUp.Shared/Core/Data/Contracts/ISteupDataService.cs @@ -8,6 +8,7 @@ public interface ISteupDataService Task Init(); Task CanOpenNewInspection(); void RegisterAppVersion(); + Task CheckAndUpdateStatus(); List PuntiVenditaList { get; } InspectionPageState InspectionPageState { get; set; } diff --git a/SteUp.Shared/Core/Data/SteupDataService.cs b/SteUp.Shared/Core/Data/SteupDataService.cs index d2d60c3..7c77b50 100644 --- a/SteUp.Shared/Core/Data/SteupDataService.cs +++ b/SteUp.Shared/Core/Data/SteupDataService.cs @@ -34,7 +34,7 @@ public class SteupDataService( ); } - private async Task CheckAndUpdateStatus() + public async Task CheckAndUpdateStatus() { var ispezioni = await ispezioniService.GetAllIspezioniWithSchedeAsync(); var listActivityId = ispezioni diff --git a/SteUp.Shared/Core/Dto/SendEmailDto.cs b/SteUp.Shared/Core/Dto/SendEmailDto.cs new file mode 100644 index 0000000..387fa6c --- /dev/null +++ b/SteUp.Shared/Core/Dto/SendEmailDto.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +namespace SteUp.Shared.Core.Dto; + +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")] + public List? Attachments { get; set; } + + public class AttachmentsDto + { + public string FileName { get; set; } = string.Empty; + public byte[] FileContent { get; set; } = []; + } +} \ No newline at end of file diff --git a/SteUp.Shared/Core/Interface/IntegryApi/IIntegryApiService.cs b/SteUp.Shared/Core/Interface/IntegryApi/IIntegryApiService.cs index ded5796..20d0cbd 100644 --- a/SteUp.Shared/Core/Interface/IntegryApi/IIntegryApiService.cs +++ b/SteUp.Shared/Core/Interface/IntegryApi/IIntegryApiService.cs @@ -7,4 +7,6 @@ public interface IIntegryApiService Task SystemOk(); Task?> SuggestActivityDescription(string activityTypeId); + + Task SendEmail(SendEmailDto sendEmail); } \ No newline at end of file diff --git a/SteUp.Shared/Core/Interface/LocalDb/IIspezioniService.cs b/SteUp.Shared/Core/Interface/LocalDb/IIspezioniService.cs index 21fb9a1..c7d30cc 100644 --- a/SteUp.Shared/Core/Interface/LocalDb/IIspezioniService.cs +++ b/SteUp.Shared/Core/Interface/LocalDb/IIspezioniService.cs @@ -24,6 +24,7 @@ public interface IIspezioniService Task GetSchedaWithIspezioneAsync(int schedaId); Task UpdateSchedaAsync(Scheda scheda); Task UpdateActivityIdSchedaAsync(int schedaId, string? activityId); + Task UpdateFileListSchedaAsync(int schedaId, List? imageNames); Task DeleteSchedaAsync(int schedaId); Task DeleteAllSchedeOfIspezioneAsync(string codMdep, DateTime data, string rilevatore); } \ No newline at end of file diff --git a/SteUp.Shared/Core/Interface/System/IAttachedService.cs b/SteUp.Shared/Core/Interface/System/IAttachedService.cs index 67e11db..87863c9 100644 --- a/SteUp.Shared/Core/Interface/System/IAttachedService.cs +++ b/SteUp.Shared/Core/Interface/System/IAttachedService.cs @@ -1,5 +1,4 @@ -using SteUp.Shared.Core.Dto; -using SteUp.Shared.Core.Entities; +using SteUp.Shared.Core.Dto; namespace SteUp.Shared.Core.Interface.System; @@ -7,22 +6,4 @@ public interface IAttachedService { Task SelectImageFromCamera(); Task?> SelectImageFromGallery(); - - Task?> GetInspectionFiles(Ispezione ispezione, List fileNameFilter, - bool includeToUpload = true, CancellationToken ct = default); - - Task SaveInspectionFile(Ispezione ispezione, byte[] file, string fileName, CancellationToken ct = default); - - bool RemoveInspectionFile(Ispezione ispezione, string fileName, bool removeAlsoFromFinal = true, - bool removeAlsoFromToUpload = true); - - Task MoveInspectionFileFromToUploadToFinal(Ispezione ispezione, string fileName, bool overwrite = true, - CancellationToken ct = default); - - Task SaveToTempStorage(Stream file, string fileName, CancellationToken ct = default); - Task CleanTempStorageAsync(CancellationToken ct = default); - Task OpenFile(string fileName, string filePath); - - Task<(string originalUrl, string thumbUrl)> SaveAndCreateThumbAsync(byte[] bytes, string fileName, - CancellationToken ct = default); } \ No newline at end of file diff --git a/SteUp.Shared/Core/Interface/System/IFileManager.cs b/SteUp.Shared/Core/Interface/System/IFileManager.cs new file mode 100644 index 0000000..8ae8331 --- /dev/null +++ b/SteUp.Shared/Core/Interface/System/IFileManager.cs @@ -0,0 +1,29 @@ +using SteUp.Shared.Core.Dto; +using SteUp.Shared.Core.Entities; + +namespace SteUp.Shared.Core.Interface.System; + +public interface IFileManager +{ + Task?> GetInspectionFiles(Ispezione ispezione, List fileNameFilter, + bool includeToUpload = true, CancellationToken ct = default); + + Task SaveInspectionFile(Ispezione ispezione, byte[] file, string fileName, CancellationToken ct = default); + + string GetFileToUploadDir(Ispezione ispezione, string fileName); + + bool RemoveInspectionFile(Ispezione ispezione, string fileName, bool removeAlsoFromFinal = true, + bool removeAlsoFromToUpload = true); + + Task MoveInspectionFileFromToUploadToFinal(Ispezione ispezione, string fileName, bool overwrite = true, + CancellationToken ct = default); + + Task SaveToTempStorage(Stream file, string fileName, CancellationToken ct = default); + Task CleanTempStorageAsync(CancellationToken ct = default); + Task OpenFile(string fileName, string filePath); + + Task<(string originalUrl, string thumbUrl)> SaveAndCreateThumbAsync(byte[] bytes, string fileName, + CancellationToken ct = default); + + List GetFileForExport(); +} \ No newline at end of file diff --git a/SteUp.Shared/Core/Services/IntegryApiService.cs b/SteUp.Shared/Core/Services/IntegryApiService.cs index 73d8220..5fd1600 100644 --- a/SteUp.Shared/Core/Services/IntegryApiService.cs +++ b/SteUp.Shared/Core/Services/IntegryApiService.cs @@ -1,6 +1,8 @@ -using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; using IntegryApiClient.Core.Domain.RestClient.Contacts; -using MudBlazor; +using Microsoft.Extensions.Logging; using SteUp.Shared.Core.Dto; using SteUp.Shared.Core.Interface.IntegryApi; @@ -8,7 +10,7 @@ namespace SteUp.Shared.Core.Services; public class IntegryApiService( IIntegryApiRestClient integryApiRestClient, - IUserSession userSession) : IIntegryApiService + ILogger logger) : IIntegryApiService { public async Task SystemOk() { @@ -19,6 +21,7 @@ public class IntegryApiService( } catch (Exception e) { + logger.LogError(e, e.Message); Console.WriteLine(e.Message); return false; } @@ -31,4 +34,44 @@ public class IntegryApiService( { "activityType", activityTypeId } } ); + + public async Task SendEmail(SendEmailDto sendEmail) + { + var content = new MultipartFormDataContent(); + + try + { + if (sendEmail.Attachments != null) + { + 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); + } + } + + sendEmail.Attachments = null; + content.Add( + new StringContent( + JsonSerializer.Serialize(sendEmail), + Encoding.UTF8, + "application/json" + ), + "request" + ); + + await integryApiRestClient.AuthorizedPost("sendEmailNew", content); + } + catch (Exception e) + { + Console.WriteLine(e); + logger.LogError(e, e.Message); + throw; + } + finally + { + content.Dispose(); + } + } } \ No newline at end of file diff --git a/SteUp.Shared/Core/Services/IntegrySteupService.cs b/SteUp.Shared/Core/Services/IntegrySteupService.cs index 613b05d..e9ffae2 100644 --- a/SteUp.Shared/Core/Services/IntegrySteupService.cs +++ b/SteUp.Shared/Core/Services/IntegrySteupService.cs @@ -56,16 +56,17 @@ public class IntegrySteupService(IIntegryApiRestClient integryApiRestClient) : I #endregion - public Task UploadFile(string activityId, byte[] file, string fileName) + public async Task UploadFile(string activityId, byte[] file, string fileName) { var queryParams = new Dictionary { { "activityId", activityId } }; - using var content = new MultipartFormDataContent(); + var content = new MultipartFormDataContent(); var fileContent = new ByteArrayContent(file); fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); content.Add(fileContent, "file", fileName); - return integryApiRestClient.Post($"{BaseRequest}/uploadAttachment", content, queryParams!); + await integryApiRestClient.Post($"{BaseRequest}/uploadAttachment", content, queryParams!); + content.Dispose(); } public Task DeleteScheda(string activityId) =>