diff --git a/GSS2.Core/Hardware/ImageStorageService.cs b/GSS2.Core/Hardware/ImageStorageService.cs index 5dfe547..f4fce68 100644 --- a/GSS2.Core/Hardware/ImageStorageService.cs +++ b/GSS2.Core/Hardware/ImageStorageService.cs @@ -26,6 +26,7 @@ public class ImageStorageService { var fileName = GenerateFileName(extension); var fullPath = GetFullPath(fileName, subDirectory); + System.Diagnostics.Debug.Assert(fullPath is not null); var directory = Path.GetDirectoryName(fullPath)!; Directory.CreateDirectory(directory); @@ -45,20 +46,25 @@ public class ImageStorageService return fileName; } - public Stream OpenRead(string fileName, string subDirectory) + public Stream OpenRead(string fileName, string? subDirectory = null) { var fullPath = GetFullPath(fileName, subDirectory); + if (fullPath is null) + throw new FileNotFoundException(null, fullPath); return new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read); } - public bool Delete(string fileName, string subDirectory) + public bool Delete(string fileName, string? subDirectory = null) { var fullPath = GetFullPath(fileName, subDirectory); + if (fullPath is null) + throw new FileNotFoundException(null, fullPath); if (!File.Exists(fullPath)) return false; File.Delete(fullPath); + _logger.LogInformation("File removed: {}", fileName); return true; } @@ -68,11 +74,29 @@ public class ImageStorageService return $"{guid}{extension}"; } - public string GetFullPath(string fileName, string subDirectory) + public string? GetFullPath(string fileName, string? subDirectory = null) { var level1 = fileName.Substring(0, 2); var level2 = fileName.Substring(2, 2); - return Path.Combine(_rootDirectory.FullName, subDirectory, level1, level2, fileName); + if (subDirectory is not null) + return Path.Combine(_rootDirectory.FullName, subDirectory, level1, level2, fileName); + + return ListFiles() + .FirstOrDefault(File.Exists); + } + + public List ListFiles(string? subDirectory = null) + { + if (subDirectory is not null) + return Directory.GetDirectories(Path.Join(_rootDirectory.FullName, subDirectory)) + .SelectMany(Directory.GetDirectories) + .SelectMany(Directory.GetFiles) + .ToList(); + + return Directory.GetDirectories(_rootDirectory.FullName) + .Select(Path.GetFileName) + .SelectMany(ListFiles) + .ToList(); } } \ No newline at end of file diff --git a/GSS2.Test/Program.cs b/GSS2.Test/Program.cs index 9a659dd..c2aa190 100644 --- a/GSS2.Test/Program.cs +++ b/GSS2.Test/Program.cs @@ -95,6 +95,12 @@ public partial class Program DefaultValueFactory = (_) => 0 }; + private static readonly Command _imageStorageClearCommand = new("image-storage-clear") + { + Description = "Clear image storage from unused images" + }; + + public static event ConsoleCancelEventHandler? ShutdownRequested; [STAThread] @@ -124,6 +130,9 @@ public partial class Program _captureCommand.Add(_captureIntencityUv365nm); _captureCommand.Add(_captureIntencityUv254nm); + _rootCommand.Add(_imageStorageClearCommand); + _imageStorageClearCommand.SetAction(ImageStorageClear); + Console.CancelKeyPress += new ConsoleCancelEventHandler(OnProgramShutdown); Console.WriteLine("Press Ctrl+C to shut down."); diff --git a/GSS2.Test/ProgramImagesStorageClear.cs b/GSS2.Test/ProgramImagesStorageClear.cs new file mode 100644 index 0000000..5b77388 --- /dev/null +++ b/GSS2.Test/ProgramImagesStorageClear.cs @@ -0,0 +1,98 @@ +using System.CommandLine; + +using GSS2.Core; +using GSS2.Core.Analysis.AmineContent.Database.Calibration; +using GSS2.Core.Hardware; +using GSS2.Core.Logging; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging.Console; + +using OpenCvSharp; + +using ConsoleFormatter = GSS2.Core.Logging.ConsoleFormatter; + +namespace GSS2.Test; + +public partial class Program +{ + private class ImagesStorateClearWorker : BackgroundService + { + private readonly ILogger _logger; + private readonly IHostApplicationLifetime _applicationLifetime; + private readonly AmineContentCalibrationContext _context; + private readonly ImageStorageService _imageStorageService; + + public ImagesStorateClearWorker(ILogger logger, IHostApplicationLifetime applicationLifetime, AmineContentCalibrationContext context, ImageStorageService imageStorageService) + { + _logger = logger; + _applicationLifetime = applicationLifetime; + _context = context; + _imageStorageService = imageStorageService; + + _logger.LogInformation("Initialized"); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("Running images storage clear"); + + var unusedFiles = _imageStorageService.ListFiles() + .Select(Path.GetFileName) + .OfType() + .Where(f => !_context.ImageRecords.Any(r => r.ImagePath == f)); + + if (unusedFiles is null || unusedFiles.Count() == 0) + { + _logger.LogInformation("Unused images not found"); + _applicationLifetime.StopApplication(); + return; + } + + _logger.LogInformation("Found {} unused image(s):\n\t{}", unusedFiles.Count(), string.Join("\n\t", unusedFiles)); + + while (true) + { + _logger.LogInformation("Delete this files? (y/n) "); + var input = Console.ReadLine()?.Trim(); + if (input == "y") + { + foreach (var file in unusedFiles) + _imageStorageService.Delete(file); + break; + } + if (input == "n") + break; + } + + _applicationLifetime.StopApplication(); + } + + } + private static void ImageStorageClear(ParseResult parseResult) + { + var builder = Host.CreateApplicationBuilder(); + + builder.Logging.ClearProviders(); + builder.Logging.AddConsole(options => options.FormatterName = nameof(ConsoleFormatter)); + builder.Logging.AddConsoleFormatter(); + builder.Logging.AddProvider(new FileLoggerProvider("full.log", true)); + builder.Logging.AddProvider(new FileLoggerProvider("last_run.log", false)); + builder.Services.AddSingleton(); + + builder.Services.AddAmineContentCalibrationContext(builder.Configuration); + builder.Services.AddImageStorageService(new DirectoryInfo("images")); + + builder.Services.AddHostedService(); + + var host = builder.Build(); + + using (var scope = host.Services.CreateScope()) + { + var context = scope.ServiceProvider.GetRequiredService(); + context.Database.Migrate(); + } + + host.Run(); + } +}