diff --git a/.vscode/launch.json b/.vscode/launch.json
index afbb38c..0af4af3 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,17 +11,6 @@
"cwd": "${workspaceFolder:Catalog}/imagecatalog",
"stopAtEntry": false,
"console": "internalConsole"
- },
- {
- "name": "CatalogLite Avalonia",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "build CatalogLite Avalonia",
- "program": "${workspaceFolder:Catalog}/CatalogLite/bin/Debug/net10.0/CatalogLite.exe",
- "args": [],
- "cwd": "${workspaceFolder:Catalog}/CatalogLite",
- "stopAtEntry": false,
- "console": "internalConsole"
}
]
}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 791bec6..22c598b 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -13,19 +13,6 @@
],
"problemMatcher": "$msCompile",
"group": "build"
- },
- {
- "label": "build CatalogLite Avalonia",
- "type": "process",
- "command": "dotnet",
- "args": [
- "build",
- "${workspaceFolder:Catalog}/CatalogLite/CatalogLite.csproj",
- "--configuration",
- "Debug"
- ],
- "problemMatcher": "$msCompile",
- "group": "build"
}
]
}
\ No newline at end of file
diff --git a/CatalogLite/Assets/Config.xml b/CatalogLite/Assets/Config.xml
deleted file mode 100644
index 9b0fa7d..0000000
--- a/CatalogLite/Assets/Config.xml
+++ /dev/null
@@ -1,227 +0,0 @@
-
-
-
- MiniatureModalita
- Text
-
-
- DirSorgente
-
-
-
- DirDestinazione
-
-
-
- MiniatureCrea
- True
-
-
- MiniatureSuffisso
- tn_
-
-
- MiniatureAltezza
- 350
-
-
- MiniatureLarghezza
- 350
-
-
- FontDimensioneMiniatura
- 20
-
-
- CompressioneJpegMiniatura
- 60
-
-
- MiniatureAddOrario
- False
-
-
- NomeMiniatura
- False
-
-
- MiniatureAddScritta
- True
-
-
- TempoSmall
- False
-
-
- NumTempoSmall
- False
-
-
- FotoCodice
-
-
-
- FotoAltezza
- 2560
-
-
- FotoLarghezza
- 2560
-
-
- FotoDimOriginali
- False
-
-
- CompressioneJpeg
- 90
-
-
- FontDimensione
- 50
-
-
- FontNome
- Verdana
-
-
- FontBold
- True
-
-
- ColoreTestoRGB
- #FA7B0A
-
-
- TestoTesto
-
-
-
- TestoVerticale
-
-
-
- TestoTrasparente
- 0
-
-
- TestoMargine
- 8
-
-
- TestoPosizione
- Basso
-
-
- TestoAllineamento
- Centro
-
-
- GrandezzaVerticale
- 18
-
-
- MargineVerticale
- 6
-
-
- MarchioAltezza
- 250
-
-
- MarchioLarghezza
- 250
-
-
- MarchioMargine
- 130
-
-
- MarchioAllOrizzontale
- Destra
-
-
- MarchioAllVerticale
- Alto
-
-
- MarchioTrasparenza
- 100
-
-
- MarchioAggiungi
- True
-
-
- ColoreTrasparente
- #FFFFFF
-
-
- UsaColoreTrasparente
- True
-
-
- ImageLibrary
- ImageSharp
-
-
- GeneraleForzaJpg
- True
-
-
- GeneraleRotazioneAutomatica
- True
-
-
- DirSottoDirectory
- True
-
-
- TempoGara
- False
-
-
- Orario
- False
-
-
- EtichettaOrario
-
-
-
- DataFoto
- False
-
-
- NumeroFoto
- False
-
-
- GeneraleSovrascriviFile
- False
-
-
- DirDividiNumFile
- 300
-
-
- DirDividiSuffisso
-
-
-
- DirDividiNumCifre
- 2
-
-
- ChunkSize
- 0
-
-
- ThreadsCount
- 0
-
-
- DataPartenza
- 17/02/2026 09:35:25
-
-
\ No newline at end of file
diff --git a/CatalogLite/Assets/Logo_RUS_ETS_tricolore_OK.png b/CatalogLite/Assets/Logo_RUS_ETS_tricolore_OK.png
deleted file mode 100644
index 1d4d8bf..0000000
Binary files a/CatalogLite/Assets/Logo_RUS_ETS_tricolore_OK.png and /dev/null differ
diff --git a/CatalogLite/Assets/testConfig.xml b/CatalogLite/Assets/testConfig.xml
deleted file mode 100644
index cf735d0..0000000
--- a/CatalogLite/Assets/testConfig.xml
+++ /dev/null
@@ -1,336 +0,0 @@
-
-
-
- MiniatureModalita
- RaceTime
-
-
- MiniatureCrea
- True
-
-
- MiniatureSuffisso
- tn_
-
-
- MiniatureAltezza
- 350
-
-
- MiniatureLarghezza
- 350
-
-
- FontDimensioneMiniatura
- 48
-
-
- CompressioneJpegMiniatura
- 25
-
-
- MiniatureAddOrario
- False
-
-
- NomeMiniatura
- False
-
-
- MiniatureAddScritta
- False
-
-
- TempoSmall
- True
-
-
- NumTempoSmall
- False
-
-
- FotoCodice
-
-
-
- FotoAltezza
- 2560
-
-
- FotoLarghezza
- 2560
-
-
- FotoDimOriginali
- False
-
-
- CompressioneJpeg
- 90
-
-
- FontDimensione
- 22
-
-
- FontNome
- Verdana
-
-
- FontBold
- True
-
-
- ColoreTestoRGB
- #FEC005
-
-
- TestoTesto
- MARATONINA DI VINCI -1 FEBBRAIO 2026
-
-
- TestoVerticale
- MARATONINA DI VINCI
-1 FEBBRAIO 2026
-
-
- TestoTrasparente
- 0
-
-
- TestoMargine
- 8
-
-
- TestoPosizione
- Basso
-
-
- TestoAllineamento
- Centro
-
-
- GrandezzaVerticale
- 18
-
-
- MargineVerticale
- 6
-
-
- MarchioFile
- K:\various\catalogtest\Logo.jpg
-
-
- MarchioAltezza
- 470
-
-
- MarchioLarghezza
- 470
-
-
- MarchioMargine
- 350
-
-
- MarchioAllOrizzontale
- Destra
-
-
- MarchioAllVerticale
- Alto
-
-
- MarchioTrasparenza
- 100
-
-
- MarchioAggiungi
- True
-
-
- ColoreTrasparente
- #FFFFFF
-
-
- UsaColoreTrasparente
- True
-
-
- ImageLibrary
- System.Graphics
-
-
- GeneraleForzaJpg
- True
-
-
- GeneraleRotazioneAutomatica
- True
-
-
- DirSottoDirectory
- True
-
-
- TempoGara
- True
-
-
- Orario
- False
-
-
- EtichettaOrario
- TEMPO :
-
-
- DataFoto
- False
-
-
- NumeroFoto
- False
-
-
- GeneraleSovrascriviFile
- True
-
-
- DirDividiNumFile
- 300
-
-
- DirDividiSuffisso
-
-
-
- DirDividiNumCifre
- 2
-
-
- ChunkSize
- 200
-
-
- ThreadsCount
- 10
-
-
- DataPartenza
- 01/02/2026 20:30:48
-
-
- AI_EstraiNumeri
- True
-
-
- AI_CartellaModelli
- K:\vs\AIFotoONLUS\models\\
-
-
- AI_PercorsoCsv
- K:\various\catalogtest\aioutput\test2.csv
-
-
- AI_UsaGpuNumeri
- True
-
-
- AI_IncludiThumbnailNumeri
- False
-
-
- AI_LivelloCaricoNumeri
- 3
-
-
- AI_FaceExecutablePath
- K:\various\regalamiunsorriso\bin\Face_Recognition_Windows\
-
-
- AI_FaceOutputFolderPath
- K:\various\catalogtest\aioutput\
-
-
- AI_FaceRecursive
- True
-
-
- AI_FaceIncludeThumbnails
- False
-
-
- AI_FaceParallelism
- 3
-
-
- AI_FaceMinSize
- 35
-
-
- AI_FaceUpsample
- False
-
-
- AI_FaceMatcherExecutablePath
- K:\various\regalamiunsorriso\bin\Face_Recognition_Windows\face_matcher.exe
-
-
- AI_FaceMatcherTolerance
- 0,75
-
-
- RaceUpload_Login
-
-
-
- RaceUpload_Password
-
-
-
- RaceUpload_Description
-
-
-
- RaceUpload_TipoGaraId
- 1
-
-
- RaceUpload_StartDate
- 12/03/2026 00:00:00
-
-
- RaceUpload_EndDate
- 12/03/2026 00:00:00
-
-
- RaceUpload_PathBase
-
-
-
- RaceUpload_Localita
-
-
-
- RaceUpload_EventoInLinea
- 0
-
-
- RaceUpload_TipoIndex
- 1
-
-
- RaceUpload_FreeEvent
- 0
-
-
- RaceUpload_LastRaceId
-
-
-
- RaceUpload_RemoteProcessedBasePath
-
-
-
\ No newline at end of file
diff --git a/CatalogLite/AsyncCommand.cs b/CatalogLite/AsyncCommand.cs
index d030d67..fcc9fe7 100644
--- a/CatalogLite/AsyncCommand.cs
+++ b/CatalogLite/AsyncCommand.cs
@@ -1,4 +1,3 @@
-using Avalonia.Threading;
using System.Windows.Input;
namespace CatalogLite;
@@ -39,14 +38,5 @@ public sealed class AsyncCommand : ICommand
}
}
- public void RaiseCanExecuteChanged()
- {
- if (Dispatcher.UIThread.CheckAccess())
- {
- CanExecuteChanged?.Invoke(this, EventArgs.Empty);
- return;
- }
-
- Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty));
- }
+ public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
\ No newline at end of file
diff --git a/CatalogLite/CatalogConfigurationLoader.cs b/CatalogLite/CatalogConfigurationLoader.cs
index a4e4a02..f7652c9 100644
--- a/CatalogLite/CatalogConfigurationLoader.cs
+++ b/CatalogLite/CatalogConfigurationLoader.cs
@@ -1,5 +1,4 @@
using System.Globalization;
-using System.Reflection;
using System.Xml.Linq;
using MaddoShared;
using SixLabors.ImageSharp;
@@ -9,9 +8,6 @@ namespace CatalogLite;
public sealed class CatalogConfigurationLoader
{
- private const string EmbeddedConfigResourceName = "CatalogLite.Assets.Config.xml";
- private const string EmbeddedLogoResourceName = "CatalogLite.Assets.Logo_RUS_ETS_tricolore_OK.png";
-
public CatalogLiteConfiguration Load(string filePath, PicSettings picSettings)
{
if (string.IsNullOrWhiteSpace(filePath))
@@ -25,7 +21,7 @@ public sealed class CatalogConfigurationLoader
}
var values = ConfigurationValues.Load(filePath);
- ApplyPicSettings(values, picSettings);
+ ApplyPicSettings(values, picSettings);
var sourcePath = LiteCatalogViewModel.NormalizeDirectoryPath(values.GetString("DirSorgente"));
var destinationPath = LiteCatalogViewModel.NormalizeDirectoryPath(values.GetString("DirDestinazione"));
@@ -45,32 +41,6 @@ public sealed class CatalogConfigurationLoader
};
}
- public CatalogLiteConfiguration LoadEmbedded(PicSettings picSettings)
- {
- using var configStream = OpenEmbeddedResource(EmbeddedConfigResourceName);
- var values = ConfigurationValues.Load(configStream);
- ApplyPicSettings(values, picSettings);
- picSettings.LogoData = ReadAllBytes(OpenEmbeddedResource(EmbeddedLogoResourceName));
- picSettings.LogoNomeFile = string.Empty;
-
- var sourcePath = LiteCatalogViewModel.NormalizeDirectoryPath(values.GetString("DirSorgente"));
- var destinationPath = LiteCatalogViewModel.NormalizeDirectoryPath(values.GetString("DirDestinazione"));
-
- picSettings.DirectorySorgente = sourcePath;
- picSettings.DirectoryDestinazione = destinationPath;
- picSettings.DestDir = string.IsNullOrWhiteSpace(destinationPath)
- ? new DirectoryInfo(Environment.CurrentDirectory)
- : new DirectoryInfo(destinationPath);
-
- return new CatalogLiteConfiguration
- {
- FilePath = "Configurazione incorporata",
- SourcePath = sourcePath,
- DestinationPath = destinationPath,
- Options = BuildOptions(values, sourcePath, destinationPath)
- };
- }
-
public static ImageCreationService.Options CloneOptions(ImageCreationService.Options options, string sourcePath, string destinationPath)
{
return new ImageCreationService.Options
@@ -180,22 +150,6 @@ public sealed class CatalogConfigurationLoader
return string.Equals(values.GetString("MiniatureModalita"), mode, StringComparison.OrdinalIgnoreCase);
}
- private static Stream OpenEmbeddedResource(string resourceName)
- {
- var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
- return stream ?? throw new InvalidOperationException($"Risorsa incorporata non trovata: {resourceName}");
- }
-
- private static byte[] ReadAllBytes(Stream stream)
- {
- using (stream)
- {
- using var memoryStream = new MemoryStream();
- stream.CopyTo(memoryStream);
- return memoryStream.ToArray();
- }
- }
-
private static Rgba32 ParseColor(string value, Rgba32 fallback)
{
if (string.IsNullOrWhiteSpace(value))
@@ -239,13 +193,7 @@ public sealed class CatalogConfigurationLoader
public static ConfigurationValues Load(string filePath)
{
- using var stream = File.OpenRead(filePath);
- return Load(stream);
- }
-
- public static ConfigurationValues Load(Stream stream)
- {
- var document = XDocument.Load(stream);
+ var document = XDocument.Load(filePath);
var values = document
.Descendants("Setup")
.Where(element => element.Element("Nome") is not null)
diff --git a/CatalogLite/CatalogLite.csproj b/CatalogLite/CatalogLite.csproj
index be2b0bc..54121b7 100644
--- a/CatalogLite/CatalogLite.csproj
+++ b/CatalogLite/CatalogLite.csproj
@@ -25,11 +25,6 @@
-
-
-
-
-
diff --git a/CatalogLite/LiteCatalogViewModel.cs b/CatalogLite/LiteCatalogViewModel.cs
index 656e4d2..0a4c8c6 100644
--- a/CatalogLite/LiteCatalogViewModel.cs
+++ b/CatalogLite/LiteCatalogViewModel.cs
@@ -12,9 +12,10 @@ public sealed class LiteCatalogViewModel : ViewModelBase
private readonly ILogger _logger;
private CatalogLiteConfiguration? _configuration;
private CancellationTokenSource? _processingTokenSource;
+ private string _configurationPath = string.Empty;
private string _sourcePath = string.Empty;
private string _destinationPath = string.Empty;
- private string _processingStatus = "Caricamento configurazione incorporata...";
+ private string _processingStatus = "Carica una configurazione XML.";
private string _speedCounter = "-";
private int _processedImagesCount;
private int _totalImagesCount;
@@ -35,23 +36,36 @@ public sealed class LiteCatalogViewModel : ViewModelBase
_imageProcessingCoordinator = imageProcessingCoordinator;
_logger = logger;
+ LoadConfigurationCommand = new AsyncCommand(RequestLoadConfigurationAsync, () => !IsProcessing);
SelectSourceFolderCommand = new AsyncCommand(RequestSourceFolderAsync, () => !IsProcessing);
SelectDestinationFolderCommand = new AsyncCommand(RequestDestinationFolderAsync, () => !IsProcessing);
StartProcessingCommand = new AsyncCommand(StartProcessingAsync, CanStartProcessing);
StopProcessingCommand = new AsyncCommand(StopProcessingAsync, () => IsProcessing);
}
+ public event EventHandler? LoadConfigurationRequested;
public event EventHandler? SelectSourceFolderRequested;
public event EventHandler? SelectDestinationFolderRequested;
public event EventHandler? ShowMessageRequested;
public Action? UiInvoker { get; set; }
+ public AsyncCommand LoadConfigurationCommand { get; }
public AsyncCommand SelectSourceFolderCommand { get; }
public AsyncCommand SelectDestinationFolderCommand { get; }
public AsyncCommand StartProcessingCommand { get; }
public AsyncCommand StopProcessingCommand { get; }
+ public string ConfigurationPath
+ {
+ get => _configurationPath;
+ private set
+ {
+ _configurationPath = value;
+ NotifyPropertyChanged();
+ }
+ }
+
public string SourcePath
{
get => _sourcePath;
@@ -63,26 +77,6 @@ public sealed class LiteCatalogViewModel : ViewModelBase
}
}
- public string HorizontalText
- {
- get => _picSettings.TestoFirmaStart ?? string.Empty;
- set
- {
- _picSettings.TestoFirmaStart = value ?? string.Empty;
- NotifyPropertyChanged();
- }
- }
-
- public string VerticalText
- {
- get => _picSettings.TestoFirmaStartV ?? string.Empty;
- set
- {
- _picSettings.TestoFirmaStartV = value ?? string.Empty;
- NotifyPropertyChanged();
- }
- }
-
public string DestinationPath
{
get => _destinationPath;
@@ -165,24 +159,25 @@ public sealed class LiteCatalogViewModel : ViewModelBase
}
}
- public void LoadEmbeddedConfiguration()
+ public async Task LoadConfigurationFromFileAsync(string filePath)
{
try
{
- var configuration = _configurationLoader.LoadEmbedded(_picSettings);
+ var configuration = await Task.Run(() => _configurationLoader.Load(filePath, _picSettings)).ConfigureAwait(false);
- _configuration = configuration;
- SourcePath = configuration.SourcePath;
- DestinationPath = configuration.DestinationPath;
- NotifyPropertyChanged(nameof(HorizontalText));
- NotifyPropertyChanged(nameof(VerticalText));
- ResetProgress("Configurazione incorporata caricata.");
+ RunOnUiThread(() =>
+ {
+ _configuration = configuration;
+ ConfigurationPath = configuration.FilePath;
+ SourcePath = configuration.SourcePath;
+ DestinationPath = configuration.DestinationPath;
+ ResetProgress("Configurazione caricata.");
+ });
}
catch (Exception ex)
{
- _logger.LogError(ex, "Errore durante il caricamento della configurazione incorporata");
- ProcessingStatus = "Errore caricamento configurazione incorporata.";
- ShowMessage("Configurazione", $"Impossibile caricare la configurazione incorporata: {ex.GetBaseException().Message}");
+ _logger.LogError(ex, "Errore durante il caricamento della configurazione");
+ ShowMessage("Configurazione", $"Impossibile caricare la configurazione: {ex.GetBaseException().Message}");
}
}
@@ -197,6 +192,12 @@ public sealed class LiteCatalogViewModel : ViewModelBase
return trimmed + Path.DirectorySeparatorChar;
}
+ private Task RequestLoadConfigurationAsync()
+ {
+ LoadConfigurationRequested?.Invoke(this, EventArgs.Empty);
+ return Task.CompletedTask;
+ }
+
private Task RequestSourceFolderAsync()
{
SelectSourceFolderRequested?.Invoke(this, EventArgs.Empty);
@@ -221,7 +222,7 @@ public sealed class LiteCatalogViewModel : ViewModelBase
{
if (_configuration is null)
{
- ShowMessage("Configurazione", "La configurazione incorporata non e' disponibile.");
+ ShowMessage("Configurazione", "Carica prima una configurazione XML.");
return;
}
@@ -339,6 +340,7 @@ public sealed class LiteCatalogViewModel : ViewModelBase
private void RaiseCommandStates()
{
+ LoadConfigurationCommand.RaiseCanExecuteChanged();
SelectSourceFolderCommand.RaiseCanExecuteChanged();
SelectDestinationFolderCommand.RaiseCanExecuteChanged();
StartProcessingCommand.RaiseCanExecuteChanged();
diff --git a/CatalogLite/MainWindow.axaml b/CatalogLite/MainWindow.axaml
index 3067595..48118bd 100644
--- a/CatalogLite/MainWindow.axaml
+++ b/CatalogLite/MainWindow.axaml
@@ -5,11 +5,24 @@
x:CompileBindings="False"
Title="Catalog Lite"
Width="740"
- Height="560"
+ Height="380"
MinWidth="640"
- MinHeight="500">
-
-
+ MinHeight="340">
+
+
+
+
+
+
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Dispatcher.UIThread.Invoke(action);
+ _viewModel.LoadConfigurationRequested += OnLoadConfigurationRequested;
_viewModel.SelectSourceFolderRequested += OnSelectSourceFolderRequested;
_viewModel.SelectDestinationFolderRequested += OnSelectDestinationFolderRequested;
_viewModel.ShowMessageRequested += OnShowMessageRequested;
- _viewModel.LoadEmbeddedConfiguration();
+ }
+
+ private async void OnLoadConfigurationRequested(object? sender, EventArgs e)
+ {
+ var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
+ {
+ Title = "Carica configurazione XML",
+ FileTypeFilter = [new FilePickerFileType("XML") { Patterns = ["*.xml"] }]
+ });
+
+ if (files.Count > 0)
+ {
+ await _viewModel.LoadConfigurationFromFileAsync(files[0].Path.LocalPath);
+ }
}
private async void OnSelectSourceFolderRequested(object? sender, EventArgs e)
@@ -55,16 +67,6 @@ public partial class MainWindow : Window
}
}
- private void OpenSourcePath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
- {
- OpenInExplorer(_viewModel.SourcePath);
- }
-
- private void OpenDestinationPath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
- {
- OpenInExplorer(_viewModel.DestinationPath);
- }
-
private async void OnShowMessageRequested(object? sender, LiteMessageEventArgs e)
{
await ShowMessageDialogAsync(e.Title, e.Message);
@@ -108,46 +110,4 @@ public partial class MainWindow : Window
closeButton.Click += (_, _) => dialog.Close();
await dialog.ShowDialog(this);
}
-
- private static void OpenInExplorer(string? path)
- {
- if (string.IsNullOrWhiteSpace(path))
- {
- return;
- }
-
- var normalizedPath = path.Trim().Trim('"');
-
- try
- {
- if (File.Exists(normalizedPath))
- {
- Process.Start("explorer.exe", $"/select,\"{normalizedPath}\"");
- return;
- }
-
- if (Directory.Exists(normalizedPath))
- {
- Process.Start(new ProcessStartInfo
- {
- FileName = normalizedPath,
- UseShellExecute = true
- });
- return;
- }
-
- var containingDirectory = Path.GetDirectoryName(normalizedPath);
- if (!string.IsNullOrWhiteSpace(containingDirectory) && Directory.Exists(containingDirectory))
- {
- Process.Start(new ProcessStartInfo
- {
- FileName = containingDirectory,
- UseShellExecute = true
- });
- }
- }
- catch
- {
- }
- }
}
\ No newline at end of file
diff --git a/MaddoShared.Tests/DataModelCharacterizationTests.cs b/MaddoShared.Tests/DataModelCharacterizationTests.cs
index 6e8c82c..ac09cb8 100644
--- a/MaddoShared.Tests/DataModelCharacterizationTests.cs
+++ b/MaddoShared.Tests/DataModelCharacterizationTests.cs
@@ -283,120 +283,9 @@ public class DataModelCharacterizationTests
output.LogFilePath.ShouldBe(@"C:\out\encoder_log_20260509_143045_04_APRILE_gara.txt");
}
- [TestMethod]
- public void FaceUploadPath_ValidatesExpectedRelativePathShape()
- {
- DataModel.IsValidFaceUploadPath("2026/05.MAGGIO/EMPOLI").ShouldBeTrue();
- DataModel.IsValidFaceUploadPath("2026/5.MAGGIO/EMPOLI").ShouldBeFalse();
- DataModel.IsValidFaceUploadPath("2026/00.MAGGIO/EMPOLI").ShouldBeFalse();
- DataModel.IsValidFaceUploadPath("2026/13.MAGGIO/EMPOLI").ShouldBeFalse();
- DataModel.IsValidFaceUploadPath("2026/05.MAGGIO").ShouldBeFalse();
-
- DataModel.CombineRemoteUploadPath("/mnt/da1/foto/", "2026/05.MAGGIO/EMPOLI")
- .ShouldBe("/mnt/da1/foto/2026/05.MAGGIO/EMPOLI");
- }
-
- [TestMethod]
- public void FaceUploadCommand_IsEnabledOnlyForValidUploadPath()
- {
- var model = CreateModel();
-
- model.UploadFaceEncoderOutputCommand.CanExecute(null).ShouldBeFalse();
-
- model.FaceUploadPath = "2026/05.MAGGIO/EMPOLI";
- model.UploadFaceEncoderOutputCommand.CanExecute(null).ShouldBeTrue();
-
- model.FaceUploadPath = "2026/5.MAGGIO/EMPOLI";
- model.UploadFaceEncoderOutputCommand.CanExecute(null).ShouldBeFalse();
- }
-
- [TestMethod]
- public void FaceSshPreferences_AreStoredInUserPreferences()
- {
- using var tempDirectory = new TemporaryDirectory();
- var preferencesFile = Path.Combine(tempDirectory.Path, "userprefs.xml");
- var preferenceService = new PickerPreferenceService(new ImageCatalog.ParametriSetup(preferencesFile));
-
- var model = CreateModel(pickerPreferenceService: preferenceService);
- model.FaceSshUsername = "ssh-user";
- model.FaceSshPassword = "ssh-password";
- model.FaceSshAddress = "upload.example.org";
- model.FaceSshPort = "2222";
- model.FaceSshPathA = "/mnt/da1/foto/";
- model.FaceSshPathB = "/mnt/nas12/foto/";
- model.FaceUploadDryRun = true;
-
- var reloadedPreferenceService = new PickerPreferenceService(new ImageCatalog.ParametriSetup(preferencesFile));
- var reloaded = CreateModel(pickerPreferenceService: reloadedPreferenceService);
-
- reloaded.FaceSshUsername.ShouldBe("ssh-user");
- reloaded.FaceSshPassword.ShouldBe("ssh-password");
- reloaded.FaceSshAddress.ShouldBe("upload.example.org");
- reloaded.FaceSshPort.ShouldBe("2222");
- reloaded.FaceSshPathA.ShouldBe("/mnt/da1/foto/");
- reloaded.FaceSshPathB.ShouldBe("/mnt/nas12/foto/");
- reloaded.FaceUploadDryRun.ShouldBeTrue();
- }
-
- [TestMethod]
- public void ResolveLatestFaceUploadSourceFile_UsesLatestPklForCurrentRace()
- {
- using var tempDirectory = new TemporaryDirectory();
- var outputFolder = Path.Combine(tempDirectory.Path, "out");
- var currentRaceFolder = Path.Combine(tempDirectory.Path, "04 APRILE gara");
- Directory.CreateDirectory(outputFolder);
- Directory.CreateDirectory(currentRaceFolder);
-
- var olderCurrentRace = Path.Combine(outputFolder, "face_encodings_20260509_143045_04_APRILE_gara.pkl");
- var newerCurrentRace = Path.Combine(outputFolder, "face_encodings_20260509_153045_04_APRILE_gara.pkl");
- var otherRace = Path.Combine(outputFolder, "face_encodings_20260509_163045_05_MAGGIO_gara.pkl");
-
- File.WriteAllText(olderCurrentRace, "old");
- File.WriteAllText(newerCurrentRace, "new");
- File.WriteAllText(otherRace, "other");
-
- File.SetLastWriteTimeUtc(olderCurrentRace, new DateTime(2026, 5, 9, 14, 30, 45, DateTimeKind.Utc));
- File.SetLastWriteTimeUtc(newerCurrentRace, new DateTime(2026, 5, 9, 15, 30, 45, DateTimeKind.Utc));
- File.SetLastWriteTimeUtc(otherRace, new DateTime(2026, 5, 9, 16, 30, 45, DateTimeKind.Utc));
-
- var selected = DataModel.ResolveLatestFaceUploadSourceFile(outputFolder, currentRaceFolder);
-
- selected.ShouldBe(newerCurrentRace);
- }
-
- [TestMethod]
- public async Task SettingsService_PersistsFaceUploadPathButNotSshPreferences()
- {
- using var tempDirectory = new TemporaryDirectory();
- var settingsFile = Path.Combine(tempDirectory.Path, "settings.xml");
- var userPreferencesFile = Path.Combine(tempDirectory.Path, "userprefs.xml");
- var preferenceService = new PickerPreferenceService(new ImageCatalog.ParametriSetup(userPreferencesFile));
- var settingsService = new SettingsService(
- new ImageCatalog.ParametriSetup(Path.Combine(tempDirectory.Path, "unused.xml")),
- Substitute.For>());
- var model = CreateModel(settingsService: settingsService, pickerPreferenceService: preferenceService);
-
- model.FaceUploadPath = "2026/05.MAGGIO/EMPOLI";
- model.FaceSshUsername = "ssh-user";
-
- await settingsService.SaveSettingsAsync(settingsFile, model);
-
- var xml = File.ReadAllText(settingsFile);
- xml.ShouldContain("AI_FaceUploadPath");
- xml.ShouldContain("2026/05.MAGGIO/EMPOLI");
- xml.ShouldNotContain("AI_FaceUploadDryRun");
- xml.ShouldNotContain("FaceAI.Ssh");
- xml.ShouldNotContain("ssh-user");
-
- var loaded = CreateModel(settingsService: settingsService);
- await settingsService.LoadSettingsAsync(settingsFile, loaded);
- loaded.FaceUploadPath.ShouldBe("2026/05.MAGGIO/EMPOLI");
- }
-
private static DataModel CreateModel(
ISettingsService? settingsService = null,
- ITestService? testService = null,
- PickerPreferenceService? pickerPreferenceService = null)
+ ITestService? testService = null)
{
var mapper = Substitute.For();
var picSettings = new PicSettings();
@@ -427,8 +316,7 @@ public class DataModelCharacterizationTests
picSettings,
mapper,
Substitute.For>(),
- versionProvider: null,
- pickerPreferenceService: pickerPreferenceService);
+ versionProvider: null);
}
private static string CreateFaceEncoderExecutable(string rootPath, string variant)
diff --git a/MaddoShared.Tests/Test1.cs b/MaddoShared.Tests/Test1.cs
index d8dc33b..da8e098 100644
--- a/MaddoShared.Tests/Test1.cs
+++ b/MaddoShared.Tests/Test1.cs
@@ -1,7 +1,6 @@
using ImageCatalog_2.Services;
using ImageCatalog_2.Models;
using Shouldly;
-using System.Text;
namespace MaddoShared.Tests;
@@ -9,7 +8,7 @@ namespace MaddoShared.Tests;
public sealed class AiExtractionServiceCsvTests
{
[TestMethod]
- public void WriteCsvOutput_UsesLegacyCompatibleQuotingAndSkipsEmptyResults()
+ public void WriteCsvOutput_UsesLegacyCompatibleHeaderAndFilenameColumn()
{
using var tempDir = new TempDirectory();
var csvPath = Path.Combine(tempDir.Path, "ocr.csv");
@@ -17,35 +16,15 @@ public sealed class AiExtractionServiceCsvTests
AiExtractionService.WriteCsvOutput(
csvPath,
[
- new AiResultItem { Path = @"C:\images\IMG_7146.JPG", Text = "43" },
- new AiResultItem { Path = @"C:\images\IMG_7207.JPG", Text = "43,84,61" },
- new AiResultItem { Path = @"C:\images\IMG_7208.JPG", Text = string.Empty },
- new AiResultItem { Path = @"C:\images\IMG_7209.JPG", Text = " " },
- new AiResultItem { Path = @"C:\images\IMG_7210.JPG", Text = "a\"b,c" }
+ new AiResultItem { Path = @"C:\images\IMG_7146.JPG", Text = "43,84,61" },
+ new AiResultItem { Path = @"C:\images\IMG_7207.JPG", Text = "a\"b" }
]);
- var csv = File.ReadAllText(csvPath, Encoding.UTF8);
+ var lines = File.ReadAllLines(csvPath);
- csv.ShouldBe("filename,text\r"
- + "IMG_7146.JPG,43\r"
- + "IMG_7207.JPG,\"43,84,61\"\r"
- + "IMG_7210.JPG,\"a\"\"b,c\"\r");
- }
-
- [TestMethod]
- public void WriteCsvOutput_WritesUtf8WithoutBom()
- {
- using var tempDir = new TempDirectory();
- var csvPath = Path.Combine(tempDir.Path, "ocr.csv");
-
- AiExtractionService.WriteCsvOutput(
- csvPath,
- [new AiResultItem { Path = @"C:\images\IMG_7146.JPG", Text = "43" }]);
-
- var bytes = File.ReadAllBytes(csvPath);
-
- bytes[.."filename,text\r".Length].ShouldBe(Encoding.ASCII.GetBytes("filename,text\r"));
- bytes[..3].ShouldNotBe(new byte[] { 0xEF, 0xBB, 0xBF });
+ lines[0].ShouldBe("filename,text");
+ lines[1].ShouldBe("\"IMG_7146.JPG\",\"43,84,61\"");
+ lines[2].ShouldBe("\"IMG_7207.JPG\",\"a\"\"b\"");
}
private sealed class TempDirectory : IDisposable
diff --git a/MaddoShared/ImageCreationStuff.cs b/MaddoShared/ImageCreationStuff.cs
index 9622b7d..7d22457 100644
--- a/MaddoShared/ImageCreationStuff.cs
+++ b/MaddoShared/ImageCreationStuff.cs
@@ -71,8 +71,8 @@ namespace MaddoShared
int threads = options.MaxThreads;
// Load logo once as raw bytes (cross-platform). byte[] is safe to share across threads.
- byte[]? logoBytes = picSettings.LogoData;
- if (logoBytes is null && picSettings.LogoAggiungi && File.Exists(picSettings.LogoNomeFile))
+ byte[]? logoBytes = null;
+ if (picSettings.LogoAggiungi && File.Exists(picSettings.LogoNomeFile))
{
logoBytes = File.ReadAllBytes(picSettings.LogoNomeFile);
}
diff --git a/MaddoShared/PicSettings.cs b/MaddoShared/PicSettings.cs
index 62acc9d..0623a7f 100644
--- a/MaddoShared/PicSettings.cs
+++ b/MaddoShared/PicSettings.cs
@@ -10,7 +10,6 @@ public class PicSettings
public string DirectoryDestinazione { get; set; }
public string TestoFirmaStart { get; set; }
public string TestoFirmaStartV { get; set; }
- public byte[]? LogoData { get; set; }
public DateTime DataPartenza { get; set; }
public string TestoOrario { get; set; }
public int DimStandard { get; set; }
diff --git a/imagecatalog/AvaloniaMainWindow.axaml b/imagecatalog/AvaloniaMainWindow.axaml
index 3b058a0..c01f37f 100644
--- a/imagecatalog/AvaloniaMainWindow.axaml
+++ b/imagecatalog/AvaloniaMainWindow.axaml
@@ -88,7 +88,7 @@
-
+
diff --git a/imagecatalog/AvaloniaMainWindow.axaml.cs b/imagecatalog/AvaloniaMainWindow.axaml.cs
index 20e5fb2..6740d6f 100644
--- a/imagecatalog/AvaloniaMainWindow.axaml.cs
+++ b/imagecatalog/AvaloniaMainWindow.axaml.cs
@@ -273,11 +273,10 @@ public partial class AvaloniaMainWindow : Window
var dialog = new Window
{
Title = title,
- Width = 960,
- Height = 560,
+ Width = 480,
CanResize = false,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
- SizeToContent = SizeToContent.Manual
+ SizeToContent = SizeToContent.Height
};
dialog.Content = BuildMessageDialogContent(message, () => dialog.Close());
@@ -312,17 +311,11 @@ public partial class AvaloniaMainWindow : Window
Spacing = 12
};
- layout.Children.Add(new ScrollViewer
+ layout.Children.Add(new TextBlock
{
- Height = 460,
- HorizontalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto,
- VerticalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto,
- Content = new TextBlock
- {
- Text = message,
- TextWrapping = Avalonia.Media.TextWrapping.Wrap,
- FontFamily = new Avalonia.Media.FontFamily("Cascadia Mono, Consolas, monospace")
- }
+ Text = message,
+ TextWrapping = Avalonia.Media.TextWrapping.Wrap,
+ MaxWidth = 420
});
var closeButton = new Button
diff --git a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml
index 4564e7d..dc1aed0 100644
--- a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml
+++ b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml
@@ -79,19 +79,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/imagecatalog/DataModel.cs b/imagecatalog/DataModel.cs
index b5ea1d6..4ed641a 100644
--- a/imagecatalog/DataModel.cs
+++ b/imagecatalog/DataModel.cs
@@ -41,7 +41,6 @@ namespace ImageCatalog_2
public ICommand StartAiCommand { get; }
public ICommand StartFaceEncoderCommand { get; }
public ICommand StopFaceEncoderCommand { get; }
- public ICommand UploadFaceEncoderOutputCommand { get; }
public ICommand StartFaceMatcherCommand { get; }
public ICommand StopFaceMatcherCommand { get; }
@@ -51,7 +50,6 @@ namespace ImageCatalog_2
private readonly ImageCreationService _imageCreationService;
private readonly IAiExtractionService _aiExtractionService;
private readonly IImageProcessingCoordinator _imageProcessingCoordinator;
- private readonly PickerPreferenceService? _pickerPreferenceService;
private readonly ProcessingStateViewModel _processing;
private readonly PathSettingsViewModel _paths;
private readonly AiSettingsViewModel _ai;
@@ -61,7 +59,6 @@ namespace ImageCatalog_2
private readonly IMapper _mapper;
private readonly AsyncCommand _startFaceEncoderCommand;
private readonly AsyncCommand _stopFaceEncoderCommand;
- private readonly AsyncCommand _uploadFaceEncoderOutputCommand;
private readonly AsyncCommand _startFaceMatcherCommand;
private readonly AsyncCommand _stopFaceMatcherCommand;
private readonly object _faceEncoderProcessLock = new();
@@ -78,15 +75,12 @@ namespace ImageCatalog_2
private Task? _faceMatcherLogWatcherTask;
private bool _hasStartedFaceEncoderInSession;
private bool _hasStartedFaceMatcherInSession;
- private bool _isLoadingFaceSshUserPreferences;
- private string _lastFaceEncoderOutputFilePath = string.Empty;
private int _numberAiGpuRefreshVersion;
private volatile bool _numberAiGpuValidationPending;
private sealed record ParsedFaceMatcherRow(string PhotoId, double? Score, string RawRow, string DebugSummary);
private const string AiCsvOverwriteDialogTitle = "File CSV gia esistente";
- private static readonly Regex FaceUploadPathRegex = new(@"^\d{4}/(?:0[1-9]|1[0-2])\.[^/\\]+/[^/\\]+$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
// ComboBox collections
public List AvailableFonts { get; }
@@ -96,7 +90,7 @@ namespace ImageCatalog_2
[CLSCompliant(false)]
public DataModel(ITestService testService, ISettingsService settingsService,
ImageCreationService imageCreationService, IAiExtractionService aiExtractionService, IImageProcessingCoordinator imageProcessingCoordinator, PicSettings picSettings,
- IMapper mapper, ILogger logger, MaddoShared.IVersionProvider? versionProvider = null, PickerPreferenceService? pickerPreferenceService = null)
+ IMapper mapper, ILogger logger, MaddoShared.IVersionProvider? versionProvider = null)
{
_service = testService;
_logger = logger;
@@ -104,7 +98,6 @@ namespace ImageCatalog_2
_imageCreationService = imageCreationService;
_aiExtractionService = aiExtractionService;
_imageProcessingCoordinator = imageProcessingCoordinator;
- _pickerPreferenceService = pickerPreferenceService;
_processing = new ProcessingStateViewModel();
_processing.PropertyChanged += OnProcessingPropertyChanged;
_paths = new PathSettingsViewModel();
@@ -129,12 +122,10 @@ namespace ImageCatalog_2
StartAiCommand = new AsyncCommand(StartAiAsync);
_startFaceEncoderCommand = new AsyncCommand(RunFaceEncoderAsync, CanRunFaceEncoder);
_stopFaceEncoderCommand = new AsyncCommand(() => StopFaceEncoderAsync("Arresto richiesto dall'utente."), CanStopFaceEncoder);
- _uploadFaceEncoderOutputCommand = new AsyncCommand(UploadFaceEncoderOutputAsync, CanUploadFaceEncoderOutput);
_startFaceMatcherCommand = new AsyncCommand(RunFaceMatcherAsync, CanRunFaceMatcher);
_stopFaceMatcherCommand = new AsyncCommand(() => StopFaceMatcherAsync("Arresto richiesto dall'utente."), CanStopFaceMatcher);
StartFaceEncoderCommand = _startFaceEncoderCommand;
StopFaceEncoderCommand = _stopFaceEncoderCommand;
- UploadFaceEncoderOutputCommand = _uploadFaceEncoderOutputCommand;
StartFaceMatcherCommand = _startFaceMatcherCommand;
StopFaceMatcherCommand = _stopFaceMatcherCommand;
@@ -148,7 +139,6 @@ namespace ImageCatalog_2
// Load available fonts
AvailableFonts = LoadAvailableFonts();
- LoadFaceSshUserPreferences();
QueueRefreshNumberAiGpuCapabilities();
RefreshFaceExecutableCapabilities();
}
@@ -340,67 +330,6 @@ namespace ImageCatalog_2
set => _ai.FaceOutputFolderPath = value;
}
- public string FaceUploadPath
- {
- get => _ai.FaceUploadPath;
- set
- {
- _ai.FaceUploadPath = value?.Trim() ?? string.Empty;
- NotifyPropertyChanged(nameof(IsFaceUploadPathValid));
- UpdateFaceUploadCommandStates();
- }
- }
-
- public bool FaceUploadDryRun
- {
- get => _ai.FaceUploadDryRun;
- set => SetFaceSshPreferenceBoolValue(PickerPreferenceKeys.FaceUploadDryRun, normalizedValue => _ai.FaceUploadDryRun = normalizedValue, value);
- }
-
- public bool IsFaceUploadPathValid => IsValidFaceUploadPath(FaceUploadPath);
-
- public string FaceSshUsername
- {
- get => _ai.FaceSshUsername;
- set => SetFaceSshPreferenceValue(PickerPreferenceKeys.FaceSshUsername, normalizedValue => _ai.FaceSshUsername = normalizedValue, value?.Trim() ?? string.Empty);
- }
-
- public string FaceSshPassword
- {
- get => _ai.FaceSshPassword;
- set => SetFaceSshPreferenceValue(PickerPreferenceKeys.FaceSshPassword, normalizedValue => _ai.FaceSshPassword = normalizedValue, value ?? string.Empty);
- }
-
- public string FaceSshAddress
- {
- get => _ai.FaceSshAddress;
- set => SetFaceSshPreferenceValue(PickerPreferenceKeys.FaceSshAddress, normalizedValue => _ai.FaceSshAddress = normalizedValue, value?.Trim() ?? string.Empty);
- }
-
- public string FaceSshPort
- {
- get => _ai.FaceSshPort;
- set => SetFaceSshPreferenceValue(PickerPreferenceKeys.FaceSshPort, normalizedValue => _ai.FaceSshPort = normalizedValue, value?.Trim() ?? string.Empty);
- }
-
- public string FaceSshPathA
- {
- get => _ai.FaceSshPathA;
- set => SetFaceSshPreferenceValue(PickerPreferenceKeys.FaceSshPathA, normalizedValue => _ai.FaceSshPathA = normalizedValue, value?.Trim() ?? string.Empty);
- }
-
- public string FaceSshPathB
- {
- get => _ai.FaceSshPathB;
- set => SetFaceSshPreferenceValue(PickerPreferenceKeys.FaceSshPathB, normalizedValue => _ai.FaceSshPathB = normalizedValue, value?.Trim() ?? string.Empty);
- }
-
- public bool IsFaceUploadRunning
- {
- get => _ai.IsFaceUploadRunning;
- private set => _ai.IsFaceUploadRunning = value;
- }
-
public bool FaceRecursive
{
get => _ai.FaceRecursive;
@@ -739,13 +668,7 @@ namespace ImageCatalog_2
}
NotifyPropertyChanged(e.PropertyName);
- if (string.Equals(e.PropertyName, nameof(AiSettingsViewModel.FaceUploadPath), StringComparison.Ordinal))
- {
- NotifyPropertyChanged(nameof(IsFaceUploadPathValid));
- }
-
UpdateFaceEncoderCommandStates();
- UpdateFaceUploadCommandStates();
UpdateFaceMatcherCommandStates();
}
@@ -1649,11 +1572,6 @@ namespace ImageCatalog_2
_stopFaceEncoderCommand?.RaiseCanExecuteChanged();
}
- private void UpdateFaceUploadCommandStates()
- {
- _uploadFaceEncoderOutputCommand?.RaiseCanExecuteChanged();
- }
-
private void UpdateFaceMatcherCommandStates()
{
_startFaceMatcherCommand?.RaiseCanExecuteChanged();
@@ -1713,7 +1631,6 @@ namespace ImageCatalog_2
var parallelism = NormalizeFaceParallelism(FaceParallelism);
var minSize = NormalizeFaceMinSize(FaceMinSize);
var outputFiles = BuildFaceEncoderOutputPaths(outputFolderPath, imagesFolder, DateTime.Now);
- _lastFaceEncoderOutputFilePath = outputFiles.OutputFilePath;
FaceExecutablePath = executableRootPath;
FaceOutputFolderPath = outputFolderPath;
@@ -1831,66 +1748,6 @@ namespace ImageCatalog_2
return !IsFaceMatcherRunning;
}
- private bool CanUploadFaceEncoderOutput()
- {
- return IsFaceUploadPathValid && !IsFaceUploadRunning;
- }
-
- private async Task UploadFaceEncoderOutputAsync()
- {
- if (!IsValidFaceUploadPath(FaceUploadPath))
- {
- FaceStatusMessage = "Percorso upload non valido.";
- return;
- }
-
- if (!TryBuildFaceUploadRequest(out var request, out var validationMessage))
- {
- FaceStatusMessage = validationMessage;
- await ShowErrorMessageAsync("Upload Face AI", validationMessage).ConfigureAwait(false);
- return;
- }
-
- IsFaceUploadRunning = true;
- UpdateFaceUploadCommandStates();
- FaceStatusMessage = "Upload face encoder in corso...";
-
- try
- {
- if (FaceUploadDryRun)
- {
- var preview = BuildFaceUploadDryRunPreview(request);
- await ShowMessageAsync("Upload Face AI (dry run)", preview).ConfigureAwait(false);
- await InvokeOnUiThreadAsync(() => FaceStatusMessage = "Dry run: comando mostrato nel popup.").ConfigureAwait(false);
- return;
- }
-
- var result = await RunFaceUploadPowerShellAsync(request).ConfigureAwait(false);
- await InvokeOnUiThreadAsync(() =>
- {
- FaceCommandOutput = string.IsNullOrWhiteSpace(FaceCommandOutput)
- ? result
- : $"{FaceCommandOutput.TrimEnd()}\n\n{result}";
- FaceStatusMessage = "Upload completato.";
- }).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- var message = ex.GetBaseException().Message;
- _logger.LogError(ex, "Face encoder upload failed.");
- await InvokeOnUiThreadAsync(() => FaceStatusMessage = "Errore durante upload face encoder.").ConfigureAwait(false);
- await ShowErrorMessageAsync("Upload Face AI", message).ConfigureAwait(false);
- }
- finally
- {
- await InvokeOnUiThreadAsync(() =>
- {
- IsFaceUploadRunning = false;
- UpdateFaceUploadCommandStates();
- }).ConfigureAwait(false);
- }
- }
-
private bool CanStopFaceMatcher()
{
return IsFaceMatcherRunning;
@@ -2504,7 +2361,7 @@ namespace ImageCatalog_2
&& File.Exists(configuration.RecognitionWeights);
}
- private Task ShowMessageAsync(string title, string message)
+ private Task ShowErrorMessageAsync(string title, string message)
{
return InvokeOnUiThreadAsync(() =>
{
@@ -2512,11 +2369,6 @@ namespace ImageCatalog_2
});
}
- private Task ShowErrorMessageAsync(string title, string message)
- {
- return ShowMessageAsync(title, message);
- }
-
internal async Task ConfirmAiCsvOverwriteIfNeededAsync()
{
var csvOutputPath = NormalizeFilePathArgument(CsvOutputPath);
@@ -3045,253 +2897,6 @@ namespace ImageCatalog_2
Path.Combine(outputFolderPath, $"encoder_log_{timestampToken}_{safeRaceName}.txt"));
}
- internal static bool IsValidFaceUploadPath(string value)
- {
- return FaceUploadPathRegex.IsMatch((value ?? string.Empty).Trim());
- }
-
- internal static string CombineRemoteUploadPath(string basePath, string relativePath)
- {
- var normalizedBasePath = (basePath ?? string.Empty).Trim().Replace('\\', '/').TrimEnd('/');
- var normalizedRelativePath = (relativePath ?? string.Empty).Trim().Replace('\\', '/').Trim('/');
-
- return string.IsNullOrWhiteSpace(normalizedBasePath)
- ? normalizedRelativePath
- : string.IsNullOrWhiteSpace(normalizedRelativePath)
- ? normalizedBasePath
- : $"{normalizedBasePath}/{normalizedRelativePath}";
- }
-
- private void LoadFaceSshUserPreferences()
- {
- if (_pickerPreferenceService is null)
- {
- return;
- }
-
- _isLoadingFaceSshUserPreferences = true;
- try
- {
- _ai.FaceSshUsername = _pickerPreferenceService.GetRememberedValue(PickerPreferenceKeys.FaceSshUsername) ?? string.Empty;
- _ai.FaceSshPassword = _pickerPreferenceService.GetRememberedRawValue(PickerPreferenceKeys.FaceSshPassword) ?? string.Empty;
- _ai.FaceSshAddress = _pickerPreferenceService.GetRememberedValue(PickerPreferenceKeys.FaceSshAddress) ?? string.Empty;
- _ai.FaceSshPort = _pickerPreferenceService.GetRememberedValue(PickerPreferenceKeys.FaceSshPort) ?? "22";
- _ai.FaceSshPathA = _pickerPreferenceService.GetRememberedValue(PickerPreferenceKeys.FaceSshPathA) ?? string.Empty;
- _ai.FaceSshPathB = _pickerPreferenceService.GetRememberedValue(PickerPreferenceKeys.FaceSshPathB) ?? string.Empty;
- _ai.FaceUploadDryRun = TryGetRememberedBoolPreference(PickerPreferenceKeys.FaceUploadDryRun);
- }
- finally
- {
- _isLoadingFaceSshUserPreferences = false;
- }
- }
-
- private void SetFaceSshPreferenceValue(string preferenceKey, Action setValue, string value)
- {
- setValue(value);
- if (_isLoadingFaceSshUserPreferences)
- {
- return;
- }
-
- _pickerPreferenceService?.RememberRawValue(preferenceKey, value);
- }
-
- private void SetFaceSshPreferenceBoolValue(string preferenceKey, Action setValue, bool value)
- {
- setValue(value);
- if (_isLoadingFaceSshUserPreferences)
- {
- return;
- }
-
- _pickerPreferenceService?.RememberRawValue(preferenceKey, value ? bool.TrueString : bool.FalseString);
- }
-
- private bool TryGetRememberedBoolPreference(string preferenceKey)
- {
- var rawValue = _pickerPreferenceService?.GetRememberedRawValue(preferenceKey);
- return bool.TryParse(rawValue, out var parsed) && parsed;
- }
-
- private bool TryBuildFaceUploadRequest(out FaceUploadRequest request, out string validationMessage)
- {
- request = null!;
- validationMessage = string.Empty;
-
- var sourceFilePath = ResolveFaceEncoderUploadSourceFile();
- if (string.IsNullOrWhiteSpace(sourceFilePath) || !File.Exists(sourceFilePath))
- {
- validationMessage = "File .pkl face encoder non trovato nella cartella output.";
- return false;
- }
-
- var username = FaceSshUsername.Trim();
- var password = FaceSshPassword;
- var serverAddress = FaceSshAddress.Trim();
- if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(serverAddress))
- {
- validationMessage = "Inserisci username e indirizzo SSH.";
- return false;
- }
-
- if (!int.TryParse(FaceSshPort.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var port) || port is < 1 or > 65535)
- {
- validationMessage = "Porta SSH non valida.";
- return false;
- }
-
- var remoteBasePaths = new[] { FaceSshPathA, FaceSshPathB }
- .Select(path => path.Trim())
- .Where(path => !string.IsNullOrWhiteSpace(path))
- .ToArray();
- if (remoteBasePaths.Length != 2)
- {
- validationMessage = "Inserisci path SSH A e path SSH B.";
- return false;
- }
-
- var relativeUploadPath = FaceUploadPath.Trim();
- var remoteDirectories = remoteBasePaths
- .Select(path => CombineRemoteUploadPath(path, relativeUploadPath))
- .ToArray();
-
- request = new FaceUploadRequest(sourceFilePath, username, password, serverAddress, port, remoteDirectories);
- return true;
- }
-
- private string? ResolveFaceEncoderUploadSourceFile()
- {
- var outputFolderPath = NormalizeDirectoryPathArgument(FaceOutputFolderPath);
- if (!Directory.Exists(outputFolderPath))
- {
- return null;
- }
-
- return ResolveLatestFaceUploadSourceFile(outputFolderPath, NormalizeDirectoryPathArgument(DestinationPath));
- }
-
- internal static string? ResolveLatestFaceUploadSourceFile(string outputFolderPath, string imagesFolderPath)
- {
- var normalizedOutputFolderPath = NormalizeDirectoryPathArgument(outputFolderPath);
- if (!Directory.Exists(normalizedOutputFolderPath))
- {
- return null;
- }
-
- var safeRaceName = BuildSafeFaceEncoderRaceName(NormalizeDirectoryPathArgument(imagesFolderPath));
- var racePattern = $"face_encodings_*_{safeRaceName}.pkl";
-
- return new DirectoryInfo(normalizedOutputFolderPath)
- .EnumerateFiles(racePattern, SearchOption.TopDirectoryOnly)
- .OrderByDescending(file => file.LastWriteTimeUtc)
- .ThenByDescending(file => file.Name, StringComparer.OrdinalIgnoreCase)
- .FirstOrDefault()
- ?.FullName;
- }
-
- private async Task RunFaceUploadPowerShellAsync(FaceUploadRequest request)
- {
- var commandLine = BuildFaceUploadPowerShellCommand(request);
-
- var processStartInfo = CreateFaceUploadProcessStartInfo(commandLine);
-
- using var process = new Process { StartInfo = processStartInfo };
-
- if (!process.Start())
- {
- throw new InvalidOperationException("Avvio PowerShell per upload fallito.");
- }
-
- await process.WaitForExitAsync().ConfigureAwait(false);
-
- if (process.ExitCode != 0)
- {
- throw new InvalidOperationException($"Upload fallito (exit code {process.ExitCode}). Verifica autenticazione SSH o inserisci la password nel terminale se richiesta.");
- }
-
- var summary = new StringBuilder();
- summary.AppendLine("Upload Face AI completato.");
- summary.AppendLine($"Command: {commandLine}");
- summary.AppendLine($"File: {request.LocalPath}");
- foreach (var remoteDirectory in request.RemoteDirectories)
- {
- summary.AppendLine($"Destinazione: {remoteDirectory}");
- }
-
- return summary.ToString();
- }
-
- private static ProcessStartInfo CreateFaceUploadProcessStartInfo(string commandLine)
- {
- var processStartInfo = new ProcessStartInfo
- {
- FileName = ResolvePowerShellExecutable(),
- UseShellExecute = false,
- RedirectStandardOutput = false,
- RedirectStandardError = false,
- RedirectStandardInput = false,
- CreateNoWindow = false
- };
-
- processStartInfo.ArgumentList.Add("-NoProfile");
- processStartInfo.ArgumentList.Add("-ExecutionPolicy");
- processStartInfo.ArgumentList.Add("Bypass");
- processStartInfo.ArgumentList.Add("-Command");
- processStartInfo.ArgumentList.Add(commandLine);
-
- return processStartInfo;
- }
-
- private static string BuildFaceUploadDryRunPreview(FaceUploadRequest request)
- {
- return BuildFaceUploadPowerShellCommand(request);
- }
-
- private static string BuildFaceUploadPowerShellCommand(FaceUploadRequest request)
- {
- var fileName = Path.GetFileName(request.LocalPath);
- var copyCommands = request.RemoteDirectories
- .Select(remoteDirectory => BuildFaceUploadCopyCommand(request, CombineRemoteUploadPath(remoteDirectory, fileName)))
- .ToArray();
-
- return string.Join("; ", copyCommands);
- }
-
- private static string ResolvePowerShellExecutable()
- {
- return OperatingSystem.IsWindows() ? "powershell.exe" : "pwsh";
- }
-
- private static string BuildFaceUploadCopyCommand(FaceUploadRequest request, string remoteFilePath)
- {
- return BuildScpCommand(request, remoteFilePath);
- }
-
- private static string BuildScpCommand(FaceUploadRequest request, string remoteFilePath)
- {
- var remoteTarget = $"{request.UserName}@{request.ServerAddress}:{QuoteScpRemotePath(remoteFilePath)}";
- return $"scp -P {request.Port.ToString(CultureInfo.InvariantCulture)} {QuotePowerShellString(request.LocalPath)} {QuotePowerShellString(remoteTarget)}";
- }
-
- private static string QuotePowerShellString(string value)
- {
- return $"'{(value ?? string.Empty).Replace("'", "''", StringComparison.Ordinal)}'";
- }
-
- private static string QuoteScpRemotePath(string value)
- {
- return "'" + (value ?? string.Empty).Replace("'", "'\\''", StringComparison.Ordinal) + "'";
- }
-
- private sealed record FaceUploadRequest(
- string LocalPath,
- string UserName,
- string Password,
- string ServerAddress,
- int Port,
- IReadOnlyList RemoteDirectories);
-
internal static string BuildSafeFaceEncoderRaceName(string imagesFolderPath)
{
var raceName = new DirectoryInfo(imagesFolderPath).Name;
diff --git a/imagecatalog/Models/SettingsDto.cs b/imagecatalog/Models/SettingsDto.cs
index 91f81cd..41c7dd5 100644
--- a/imagecatalog/Models/SettingsDto.cs
+++ b/imagecatalog/Models/SettingsDto.cs
@@ -289,10 +289,6 @@ namespace ImageCatalog_2.Models
[XmlElement("AI_FaceOutputFolderPath")]
public string FaceOutputFolderPath { get; set; } = string.Empty;
- [JsonPropertyName("FaceUploadPath")]
- [XmlElement("AI_FaceUploadPath")]
- public string FaceUploadPath { get; set; } = string.Empty;
-
[JsonPropertyName("FaceRecursive")]
[XmlElement("AI_FaceRecursive")]
public bool FaceRecursive { get; set; }
diff --git a/imagecatalog/Program.cs b/imagecatalog/Program.cs
index 997a47c..aa02e40 100644
--- a/imagecatalog/Program.cs
+++ b/imagecatalog/Program.cs
@@ -136,9 +136,8 @@ static class Program
var mapper = sp.GetRequiredService();
var logger = sp.GetRequiredService>();
var versionProvider = sp.GetService();
- var pickerPreferenceService = sp.GetRequiredService();
- return new DataModel(testService, settingsService, imageCreation, aiExtractionService, imageProcessingCoordinator, picSettings, mapper, logger, versionProvider, pickerPreferenceService);
+ return new DataModel(testService, settingsService, imageCreation, aiExtractionService, imageProcessingCoordinator, picSettings, mapper, logger, versionProvider);
});
services.AddTransient();
diff --git a/imagecatalog/Services/AiExtractionService.cs b/imagecatalog/Services/AiExtractionService.cs
index b8f413a..685a43f 100644
--- a/imagecatalog/Services/AiExtractionService.cs
+++ b/imagecatalog/Services/AiExtractionService.cs
@@ -217,31 +217,16 @@ public class AiExtractionService : IAiExtractionService
Directory.CreateDirectory(dir);
}
- using var sw = new StreamWriter(csvOutputPath, false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
- sw.NewLine = "\r";
+ using var sw = new StreamWriter(csvOutputPath, false, Encoding.UTF8);
sw.WriteLine("filename,text");
foreach (var result in extractedResults)
{
- var text = result.Text ?? string.Empty;
- if (string.IsNullOrWhiteSpace(text))
- {
- continue;
- }
-
var csvFileName = Path.GetFileName(result.Path ?? string.Empty);
- sw.Write(csvFileName);
- sw.Write(',');
- sw.WriteLine(FormatLegacyTextField(text));
+ var safeText = (result.Text ?? string.Empty).Replace("\"", "\"\"");
+ sw.WriteLine($"\"{csvFileName}\",\"{safeText}\"");
}
}
- private static string FormatLegacyTextField(string text)
- {
- return text.Contains(',', StringComparison.Ordinal)
- ? $"\"{text.Replace("\"", "\"\"")}\""
- : text;
- }
-
private static double CalculateAverageImagesPerSecond(int processed, TimeSpan elapsed)
{
return elapsed.TotalSeconds > 0 ? processed / elapsed.TotalSeconds : 0;
diff --git a/imagecatalog/Services/PickerPreferenceService.cs b/imagecatalog/Services/PickerPreferenceService.cs
index 785ee4f..49bdaea 100644
--- a/imagecatalog/Services/PickerPreferenceService.cs
+++ b/imagecatalog/Services/PickerPreferenceService.cs
@@ -22,13 +22,6 @@ public static class PickerPreferenceKeys
public const string FaceMatcherEncodings = "Picker.FaceMatcherEncodings.LastPath";
public const string FaceMatcherOutput = "Picker.FaceMatcherOutput.LastPath";
public const string FaceMatcherLog = "Picker.FaceMatcherLog.LastPath";
- public const string FaceSshUsername = "FaceAI.Ssh.Username";
- public const string FaceSshPassword = "FaceAI.Ssh.Password";
- public const string FaceSshAddress = "FaceAI.Ssh.Address";
- public const string FaceSshPort = "FaceAI.Ssh.Port";
- public const string FaceSshPathA = "FaceAI.Ssh.PathA";
- public const string FaceSshPathB = "FaceAI.Ssh.PathB";
- public const string FaceUploadDryRun = "FaceAI.Upload.DryRun";
}
public sealed class PickerPreferenceService
@@ -78,13 +71,6 @@ public sealed class PickerPreferenceService
: value.Trim().Trim('"');
}
- public string? GetRememberedRawValue(string preferenceKey)
- {
- return _userPreferences.ParametroExists(preferenceKey)
- ? _userPreferences.LeggiParametroString(preferenceKey)
- : null;
- }
-
public void RememberValue(string preferenceKey, string? value)
{
if (string.IsNullOrWhiteSpace(value))
@@ -96,12 +82,6 @@ public sealed class PickerPreferenceService
_userPreferences.SalvaParametriSetup();
}
- public void RememberRawValue(string preferenceKey, string? value)
- {
- _userPreferences.AggiornaParametro(preferenceKey, value ?? string.Empty);
- _userPreferences.SalvaParametriSetup();
- }
-
public void ForgetValue(string preferenceKey)
{
if (_userPreferences.RimuoviParametro(preferenceKey))
diff --git a/imagecatalog/ViewModels/AiSettingsViewModel.cs b/imagecatalog/ViewModels/AiSettingsViewModel.cs
index 118d577..e38213c 100644
--- a/imagecatalog/ViewModels/AiSettingsViewModel.cs
+++ b/imagecatalog/ViewModels/AiSettingsViewModel.cs
@@ -115,105 +115,6 @@ public class AiSettingsViewModel : ViewModelBase
}
}
- private string _faceUploadPath = string.Empty;
- public string FaceUploadPath
- {
- get => _faceUploadPath;
- set
- {
- _faceUploadPath = value;
- NotifyPropertyChanged();
- }
- }
-
- private bool _faceUploadDryRun;
- public bool FaceUploadDryRun
- {
- get => _faceUploadDryRun;
- set
- {
- _faceUploadDryRun = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceSshUsername = string.Empty;
- public string FaceSshUsername
- {
- get => _faceSshUsername;
- set
- {
- _faceSshUsername = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceSshPassword = string.Empty;
- public string FaceSshPassword
- {
- get => _faceSshPassword;
- set
- {
- _faceSshPassword = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceSshAddress = string.Empty;
- public string FaceSshAddress
- {
- get => _faceSshAddress;
- set
- {
- _faceSshAddress = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceSshPort = "22";
- public string FaceSshPort
- {
- get => _faceSshPort;
- set
- {
- _faceSshPort = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceSshPathA = string.Empty;
- public string FaceSshPathA
- {
- get => _faceSshPathA;
- set
- {
- _faceSshPathA = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceSshPathB = string.Empty;
- public string FaceSshPathB
- {
- get => _faceSshPathB;
- set
- {
- _faceSshPathB = value;
- NotifyPropertyChanged();
- }
- }
-
- private bool _isFaceUploadRunning;
- public bool IsFaceUploadRunning
- {
- get => _isFaceUploadRunning;
- set
- {
- _isFaceUploadRunning = value;
- NotifyPropertyChanged();
- }
- }
-
private bool _faceRecursive;
public bool FaceRecursive
{