diff --git a/.gitignore b/.gitignore
index 7c86da2..7e95a94 100644
--- a/.gitignore
+++ b/.gitignore
@@ -256,5 +256,4 @@ paket-files/
.idea/
*.sln.iml
.vscode/settings.json
-tmp/**
-TestArtifacts/**
\ No newline at end of file
+tmp/**
\ No newline at end of file
diff --git a/Catalog.Communication/Catalog.Communication.csproj b/Catalog.Communication/Catalog.Communication.csproj
index 8b3c8df..cb8dc94 100644
--- a/Catalog.Communication/Catalog.Communication.csproj
+++ b/Catalog.Communication/Catalog.Communication.csproj
@@ -8,10 +8,10 @@
-
-
-
-
+
+
+
+
diff --git a/Catalog.code-workspace b/Catalog.code-workspace
index 93418a7..15ec3a0 100644
--- a/Catalog.code-workspace
+++ b/Catalog.code-workspace
@@ -11,7 +11,6 @@
}
],
"settings": {
- "commentTranslate.hover.enabled": false,
- "github.copilot.chat.otel.dbSpanExporter.enabled": true
+ "commentTranslate.hover.enabled": false
}
}
\ No newline at end of file
diff --git a/MaddoShared.Benchmarks/MaddoShared.Benchmarks.csproj b/MaddoShared.Benchmarks/MaddoShared.Benchmarks.csproj
index 56b8684..b712ff3 100644
--- a/MaddoShared.Benchmarks/MaddoShared.Benchmarks.csproj
+++ b/MaddoShared.Benchmarks/MaddoShared.Benchmarks.csproj
@@ -10,8 +10,8 @@
-
-
+
+
diff --git a/MaddoShared.ImageSharpTests/MaddoShared.ImageSharpTests.csproj b/MaddoShared.ImageSharpTests/MaddoShared.ImageSharpTests.csproj
index 6a7b6fe..52f36f6 100644
--- a/MaddoShared.ImageSharpTests/MaddoShared.ImageSharpTests.csproj
+++ b/MaddoShared.ImageSharpTests/MaddoShared.ImageSharpTests.csproj
@@ -9,15 +9,15 @@
-
-
-
+
+
+
-
-
-
-
+
+
+
+
diff --git a/MaddoShared.Tests/DataModelCharacterizationTests.cs b/MaddoShared.Tests/DataModelCharacterizationTests.cs
index ac09cb8..0a94ae9 100644
--- a/MaddoShared.Tests/DataModelCharacterizationTests.cs
+++ b/MaddoShared.Tests/DataModelCharacterizationTests.cs
@@ -96,45 +96,6 @@ public class DataModelCharacterizationTests
model.DestinationPath.ShouldBe($"C:{System.IO.Path.DirectorySeparatorChar}output{System.IO.Path.DirectorySeparatorChar}");
}
- [TestMethod]
- public void DestinationPathChange_UpdatesAiCsvFileNameToMatchFinalFolderSegment()
- {
- var model = CreateModel();
- model.CsvOutputPath = @"K:\various\catalogtest\aioutput\test2.csv";
-
- model.DestinationPath = @"K:\various\catalogtest\Dest\03.KM_8_A\";
-
- model.CsvOutputPath.ShouldBe(@"K:\various\catalogtest\aioutput\03.KM_8_A.csv");
- }
-
- [TestMethod]
- public async Task ConfirmAiCsvOverwriteIfNeededAsync_CanCancelWhenCsvAlreadyExists()
- {
- using var tempDirectory = new TemporaryDirectory();
- var csvPath = Path.Combine(tempDirectory.Path, "existing.csv");
- File.WriteAllText(csvPath, "existing");
-
- var model = CreateModel();
- model.CsvOutputPath = csvPath;
-
- string? requestedTitle = null;
- string? requestedMessage = null;
- model.ConfirmAiCsvOverwriteAsync = (title, message) =>
- {
- requestedTitle = title;
- requestedMessage = message;
- return Task.FromResult(false);
- };
-
- var shouldContinue = await model.ConfirmAiCsvOverwriteIfNeededAsync();
-
- shouldContinue.ShouldBeFalse();
- requestedTitle.ShouldBe("File CSV gia esistente");
- requestedMessage.ShouldNotBeNull();
- requestedMessage.ShouldContain("Vuoi sovrascriverlo?");
- requestedMessage.ShouldContain(csvPath);
- }
-
[TestMethod]
public void AiChildChange_RaisesDataModelPropertyChanged()
{
diff --git a/MaddoShared.Tests/MaddoShared.Tests.csproj b/MaddoShared.Tests/MaddoShared.Tests.csproj
index 2c8d370..d8936a9 100644
--- a/MaddoShared.Tests/MaddoShared.Tests.csproj
+++ b/MaddoShared.Tests/MaddoShared.Tests.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/MaddoShared.Tests/PickerPreferenceServiceTests.cs b/MaddoShared.Tests/PickerPreferenceServiceTests.cs
deleted file mode 100644
index f0fe3d0..0000000
--- a/MaddoShared.Tests/PickerPreferenceServiceTests.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System;
-using System.IO;
-using ImageCatalog;
-using ImageCatalog_2.Services;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Shouldly;
-
-namespace MaddoShared.Tests;
-
-[TestClass]
-public class PickerPreferenceServiceTests
-{
- [TestMethod]
- public void RememberValue_PersistsExactFilePath()
- {
- using var tempDirectory = new TemporaryDirectory();
- var preferencesFile = Path.Combine(tempDirectory.Path, "userprefs.xml");
- var service = new PickerPreferenceService(new ParametriSetup(preferencesFile));
- var settingsFile = Path.Combine(tempDirectory.Path, "nested", "settings.xml");
-
- service.RememberValue(PickerPreferenceKeys.LastSettingsFile, settingsFile);
-
- service.GetRememberedValue(PickerPreferenceKeys.LastSettingsFile).ShouldBe(settingsFile);
- }
-
- [TestMethod]
- public void ForgetValue_RemovesStoredPreference()
- {
- using var tempDirectory = new TemporaryDirectory();
- var preferencesFile = Path.Combine(tempDirectory.Path, "userprefs.xml");
- var service = new PickerPreferenceService(new ParametriSetup(preferencesFile));
-
- service.RememberValue(PickerPreferenceKeys.LastSettingsFile, Path.Combine(tempDirectory.Path, "settings.xml"));
-
- service.ForgetValue(PickerPreferenceKeys.LastSettingsFile);
-
- service.GetRememberedValue(PickerPreferenceKeys.LastSettingsFile).ShouldBeNull();
- }
-
- private sealed class TemporaryDirectory : IDisposable
- {
- public TemporaryDirectory()
- {
- Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName());
- Directory.CreateDirectory(Path);
- }
-
- public string Path { get; }
-
- public void Dispose()
- {
- if (Directory.Exists(Path))
- {
- Directory.Delete(Path, recursive: true);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/MaddoShared.Tests/Test1.cs b/MaddoShared.Tests/Test1.cs
index da8e098..cd00c92 100644
--- a/MaddoShared.Tests/Test1.cs
+++ b/MaddoShared.Tests/Test1.cs
@@ -1,48 +1,11 @@
-using ImageCatalog_2.Services;
-using ImageCatalog_2.Models;
-using Shouldly;
-
-namespace MaddoShared.Tests;
-
-[TestClass]
-public sealed class AiExtractionServiceCsvTests
+namespace MaddoShared.Tests
{
- [TestMethod]
- public void WriteCsvOutput_UsesLegacyCompatibleHeaderAndFilenameColumn()
+ [TestClass]
+ public sealed class Test1
{
- 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,84,61" },
- new AiResultItem { Path = @"C:\images\IMG_7207.JPG", Text = "a\"b" }
- ]);
-
- var lines = File.ReadAllLines(csvPath);
-
- 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
- {
- public TempDirectory()
+ [TestMethod]
+ public void TestMethod1()
{
- Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName());
- Directory.CreateDirectory(Path);
- }
-
- public string Path { get; }
-
- public void Dispose()
- {
- if (Directory.Exists(Path))
- {
- Directory.Delete(Path, recursive: true);
- }
}
}
}
diff --git a/MaddoShared/MaddoShared.csproj b/MaddoShared/MaddoShared.csproj
index 7e3998c..b18b80a 100644
--- a/MaddoShared/MaddoShared.csproj
+++ b/MaddoShared/MaddoShared.csproj
@@ -2,8 +2,6 @@
net10.0
Library
- enable
- enable
false
x64
diff --git a/imagecatalog/AvaloniaApp.axaml b/imagecatalog/AvaloniaApp.axaml
index fc57b97..97bd721 100644
--- a/imagecatalog/AvaloniaApp.axaml
+++ b/imagecatalog/AvaloniaApp.axaml
@@ -100,7 +100,7 @@
-
+
diff --git a/imagecatalog/AvaloniaMainWindow.axaml b/imagecatalog/AvaloniaMainWindow.axaml
index c01f37f..ba21b00 100644
--- a/imagecatalog/AvaloniaMainWindow.axaml
+++ b/imagecatalog/AvaloniaMainWindow.axaml
@@ -5,7 +5,6 @@
xmlns:views="clr-namespace:ImageCatalog_2.AvaloniaViews"
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
x:Class="ImageCatalog_2.AvaloniaMainWindow"
- x:CompileBindings="False"
mc:Ignorable="d"
Title="Image Catalog - Avalonia" Height="540" Width="800">
@@ -107,7 +106,7 @@
-
+
diff --git a/imagecatalog/AvaloniaViews/AiTabView.axaml.cs b/imagecatalog/AvaloniaViews/AiTabView.axaml.cs
index f56e327..f89ded7 100644
--- a/imagecatalog/AvaloniaViews/AiTabView.axaml.cs
+++ b/imagecatalog/AvaloniaViews/AiTabView.axaml.cs
@@ -1,4 +1,8 @@
using Avalonia.Controls;
+using Avalonia.Interactivity;
+using System.Diagnostics;
+using System.IO;
+
namespace ImageCatalog_2.AvaloniaViews;
public partial class AiTabView : Avalonia.Controls.UserControl
@@ -7,4 +11,56 @@ public partial class AiTabView : Avalonia.Controls.UserControl
{
InitializeComponent();
}
+
+ private void OpenModelsFolder_Click(object? sender, RoutedEventArgs e)
+ {
+ if (DataContext is DataModel model)
+ {
+ OpenInExplorer(model.ModelsFolderPath);
+ }
+ }
+
+ private void OpenCsvOutputFolder_Click(object? sender, RoutedEventArgs e)
+ {
+ if (DataContext is not DataModel model)
+ {
+ return;
+ }
+
+ var directory = Path.GetDirectoryName(model.CsvOutputPath);
+ OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? model.CsvOutputPath : directory);
+ }
+
+ private void OpenAiDestinationFolder_Click(object? sender, RoutedEventArgs e)
+ {
+ if (DataContext is DataModel model)
+ {
+ OpenInExplorer(model.DestinationPath);
+ }
+ }
+
+ 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}\"");
+ }
+ else if (Directory.Exists(normalizedPath))
+ {
+ Process.Start(new ProcessStartInfo { FileName = normalizedPath, UseShellExecute = true });
+ }
+ }
+ catch
+ {
+ // Ignore failures when opening Explorer.
+ }
+ }
}
diff --git a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml
index dc1aed0..134621c 100644
--- a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml
+++ b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml
@@ -1,291 +1,109 @@
-
-
-
+ x:Class="ImageCatalog_2.AvaloniaViews.FaceAiTabView">
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml.cs b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml.cs
index 0fbe36b..3057b02 100644
--- a/imagecatalog/AvaloniaViews/FaceAiTabView.axaml.cs
+++ b/imagecatalog/AvaloniaViews/FaceAiTabView.axaml.cs
@@ -1,17 +1,11 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
-using Avalonia.Input;
-using Avalonia.Layout;
-using Avalonia.Media;
-using Avalonia.Media.Imaging;
+using Avalonia.Platform.Storage;
using Avalonia.Threading;
-using ImageCatalog_2.Models;
-using ImageCatalog_2.Services;
using System;
using System.ComponentModel;
+using System.Diagnostics;
using System.IO;
-using System.Text;
-using System.Threading.Tasks;
namespace ImageCatalog_2.AvaloniaViews;
@@ -41,21 +35,12 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
private void OnFaceAiPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
- if (string.Equals(e.PropertyName, nameof(DataModel.FaceCommandOutput), StringComparison.Ordinal))
+ if (!string.Equals(e.PropertyName, nameof(DataModel.FaceCommandOutput), StringComparison.Ordinal))
{
- ScrollOutputTextBoxToEnd("FaceOutputTextBox");
return;
}
- if (string.Equals(e.PropertyName, nameof(DataModel.FaceMatcherCommandOutput), StringComparison.Ordinal))
- {
- ScrollOutputTextBoxToEnd("FaceMatcherOutputTextBox");
- }
- }
-
- private void ScrollOutputTextBoxToEnd(string controlName)
- {
- var outputBox = this.FindControl(controlName);
+ var outputBox = this.FindControl("FaceOutputTextBox");
if (outputBox is null)
{
return;
@@ -68,258 +53,172 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
});
}
- private async void OpenFaceMatcherPreview_Click(object? sender, RoutedEventArgs e)
+ private async void SelectFaceExecutable_Click(object? sender, RoutedEventArgs e)
{
- if (sender is not Button { Tag: FaceMatcherResultItem item })
+ var executableBox = this.FindControl("FaceExecutablePathTextBox");
+ if (executableBox is null)
{
return;
}
- await OpenFaceMatcherPreviewAsync(item);
- }
-
- private async Task OpenFaceMatcherPreviewAsync(FaceMatcherResultItem item)
- {
- var owner = TopLevel.GetTopLevel(this) as Window;
- var dialog = BuildFaceMatcherPreviewDialog(item);
- if (owner is not null)
- {
- await dialog.ShowDialog(owner);
- return;
- }
-
- dialog.Show();
- }
-
- private async void FaceMatcherResults_DoubleTapped(object? sender, TappedEventArgs e)
- {
- if (sender is not Avalonia.Controls.DataGrid { SelectedItem: FaceMatcherResultItem item })
+ var topLevel = TopLevel.GetTopLevel(this);
+ var storageProvider = topLevel?.StorageProvider;
+ if (storageProvider is null)
{
return;
}
- await OpenFaceMatcherPreviewAsync(item);
+ var folders = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
+ {
+ Title = "Seleziona la cartella Face Recognition Windows"
+ });
+
+ if (folders.Count > 0)
+ {
+ executableBox.Text = folders[0].Path.LocalPath;
+ if (DataContext is DataModel model)
+ {
+ model.FaceExecutablePath = executableBox.Text;
+ }
+ }
}
- private Window BuildFaceMatcherPreviewDialog(FaceMatcherResultItem item)
+ private async void SelectFaceOutputFolder_Click(object? sender, RoutedEventArgs e)
{
- var dialog = new Window
+ var outputBox = this.FindControl("FaceOutputFolderTextBox");
+ if (outputBox is null)
{
- Title = $"Preview match: {item.PhotoId}",
- Width = 1180,
- Height = 900,
- WindowStartupLocation = WindowStartupLocation.CenterOwner
- };
-
- Bitmap? bitmap = null;
- var dimensionText = "n/d";
- if (!string.IsNullOrWhiteSpace(item.ResolvedImagePath) && File.Exists(item.ResolvedImagePath))
- {
- try
- {
- bitmap = new Bitmap(item.ResolvedImagePath);
- dimensionText = $"{bitmap.PixelSize.Width} x {bitmap.PixelSize.Height}px";
- }
- catch
- {
- bitmap = null;
- }
+ return;
}
- var fileInfo = !string.IsNullOrWhiteSpace(item.ResolvedImagePath) && File.Exists(item.ResolvedImagePath)
- ? new FileInfo(item.ResolvedImagePath)
- : null;
-
- var debugBuilder = new StringBuilder();
- debugBuilder.AppendLine($"File matcher: {item.PhotoId}");
- debugBuilder.AppendLine($"Score: {item.ScoreDisplay}");
- debugBuilder.AppendLine($"Path risolto: {item.ResolvedImagePath}");
- debugBuilder.AppendLine($"Candidati trovati in destinazione: {item.CandidateCount}");
- debugBuilder.AppendLine($"Dimensioni immagine: {dimensionText}");
- if (fileInfo is not null)
+ var topLevel = TopLevel.GetTopLevel(this);
+ var storageProvider = topLevel?.StorageProvider;
+ if (storageProvider is null)
{
- debugBuilder.AppendLine($"Dimensione file: {fileInfo.Length / 1024.0:F1} KB");
- debugBuilder.AppendLine($"Ultima modifica: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
+ return;
}
- debugBuilder.AppendLine($"Immagine ricerca: {item.SearchImagePath}");
- debugBuilder.AppendLine($"CSV risultati: {item.CsvPath}");
- debugBuilder.AppendLine($"Log matcher: {item.LogPath}");
- if (!string.IsNullOrWhiteSpace(item.DebugSummary))
+ var folders = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
- debugBuilder.AppendLine($"Dettagli riga: {item.DebugSummary}");
- }
-
- if (!string.IsNullOrWhiteSpace(item.RawRow))
- {
- debugBuilder.AppendLine($"Raw CSV: {item.RawRow}");
- }
-
- var layout = new Grid
- {
- Margin = new Avalonia.Thickness(16),
- RowDefinitions = new RowDefinitions("Auto,*,Auto")
- };
-
- var header = new StackPanel { Spacing = 6 };
- header.Children.Add(new TextBlock
- {
- Text = item.PhotoId,
- FontWeight = FontWeight.Bold,
- FontSize = 18
+ Title = "Seleziona la cartella output per encodings e log"
});
- header.Children.Add(new TextBlock
- {
- Text = string.IsNullOrWhiteSpace(item.ScoreDisplay)
- ? "Score: n/d"
- : $"Score: {item.ScoreDisplay}%",
- FontWeight = FontWeight.SemiBold,
- Opacity = 0.9
- });
- header.Children.Add(new TextBlock
- {
- Text = string.IsNullOrWhiteSpace(item.ResolvedImagePath)
- ? "Nessun file immagine risolto nella cartella Destinazione."
- : item.ResolvedImagePath,
- TextWrapping = TextWrapping.Wrap,
- Opacity = 0.8
- });
- layout.Children.Add(header);
- var contentGrid = new Grid
+ if (folders.Count > 0)
{
- Margin = new Avalonia.Thickness(0, 12, 0, 12),
- RowDefinitions = new RowDefinitions("Auto,*,Auto")
- };
- Grid.SetRow(contentGrid, 1);
-
- var zoomLevel = 1.0;
- var zoomText = new TextBlock
- {
- Text = "100%",
- VerticalAlignment = VerticalAlignment.Center,
- MinWidth = 52,
- TextAlignment = TextAlignment.Center
- };
-
- var imageControl = bitmap is null
- ? null
- : new Image
+ outputBox.Text = folders[0].Path.LocalPath;
+ if (DataContext is DataModel model)
{
- Source = bitmap,
- Stretch = Stretch.None,
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- RenderTransform = new ScaleTransform(1, 1)
- };
-
- void UpdateZoom(double delta)
- {
- if (imageControl is null)
- {
- return;
+ model.FaceOutputFolderPath = outputBox.Text;
}
-
- zoomLevel = Math.Clamp(zoomLevel + delta, 0.1, 8.0);
- imageControl.RenderTransform = new ScaleTransform(zoomLevel, zoomLevel);
- zoomText.Text = $"{zoomLevel * 100:0}%";
}
-
- var toolbar = new StackPanel
- {
- Orientation = Orientation.Horizontal,
- Spacing = 8,
- Margin = new Avalonia.Thickness(0, 0, 0, 12)
- };
-
- var zoomOutButton = new Button { Content = "Zoom -", MinWidth = 80, IsEnabled = imageControl is not null };
- zoomOutButton.Click += (_, _) => UpdateZoom(-0.1);
- toolbar.Children.Add(zoomOutButton);
-
- var zoomInButton = new Button { Content = "Zoom +", MinWidth = 80, IsEnabled = imageControl is not null };
- zoomInButton.Click += (_, _) => UpdateZoom(0.1);
- toolbar.Children.Add(zoomInButton);
-
- var resetZoomButton = new Button { Content = "100%", MinWidth = 72, IsEnabled = imageControl is not null };
- resetZoomButton.Click += (_, _) =>
- {
- if (imageControl is null)
- {
- return;
- }
-
- zoomLevel = 1.0;
- imageControl.RenderTransform = new ScaleTransform(1, 1);
- zoomText.Text = "100%";
- };
- toolbar.Children.Add(resetZoomButton);
- toolbar.Children.Add(zoomText);
- contentGrid.Children.Add(toolbar);
-
- var previewBorder = new Border
- {
- BorderBrush = Brushes.Gray,
- BorderThickness = new Avalonia.Thickness(1),
- Padding = new Avalonia.Thickness(8),
- Child = new ScrollViewer
- {
- HorizontalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto,
- VerticalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto,
- Content = bitmap is null
- ? new TextBlock
- {
- Text = "Anteprima non disponibile",
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center
- }
- : imageControl
- }
- };
- Grid.SetRow(previewBorder, 1);
- contentGrid.Children.Add(previewBorder);
-
- var debugBox = new TextBox
- {
- Text = debugBuilder.ToString(),
- IsReadOnly = true,
- AcceptsReturn = true,
- TextWrapping = TextWrapping.Wrap,
- FontFamily = new FontFamily("Cascadia Mono, Consolas, monospace"),
- MinHeight = 180,
- Margin = new Avalonia.Thickness(0, 12, 0, 0)
- };
- Grid.SetRow(debugBox, 2);
- contentGrid.Children.Add(debugBox);
-
- layout.Children.Add(contentGrid);
-
- var footer = new StackPanel
- {
- Orientation = Orientation.Horizontal,
- HorizontalAlignment = HorizontalAlignment.Right,
- Spacing = 8
- };
- Grid.SetRow(footer, 2);
-
- var openFileButton = new Button { Content = "Apri file" };
- openFileButton.Click += (_, _) => PathShellService.OpenInExplorer(item.ResolvedImagePath);
- footer.Children.Add(openFileButton);
-
- var openFolderButton = new Button { Content = "Apri cartella" };
- openFolderButton.Click += (_, _) =>
- {
- var directory = Path.GetDirectoryName(item.ResolvedImagePath);
- PathShellService.OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? item.ResolvedImagePath : directory);
- };
- footer.Children.Add(openFolderButton);
-
- var closeButton = new Button { Content = "Chiudi", MinWidth = 88 };
- closeButton.Click += (_, _) => dialog.Close();
- footer.Children.Add(closeButton);
-
- layout.Children.Add(footer);
- dialog.Content = layout;
- return dialog;
}
+ private void OpenFaceExecutableFolder_Click(object? sender, RoutedEventArgs e)
+ {
+ var executableBox = this.FindControl("FaceExecutablePathTextBox");
+ if (executableBox is null)
+ {
+ return;
+ }
+
+ var path = executableBox.Text?.Trim();
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ return;
+ }
+
+ if (Directory.Exists(path))
+ {
+ OpenInExplorer(path);
+ return;
+ }
+
+ if (File.Exists(path))
+ {
+ OpenInExplorer(path);
+ return;
+ }
+
+ var directory = Path.GetDirectoryName(path);
+ OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? path : directory);
+ }
+
+ private void OpenFaceOutputFolder_Click(object? sender, RoutedEventArgs e)
+ {
+ var outputBox = this.FindControl("FaceOutputFolderTextBox");
+ if (outputBox is null)
+ {
+ return;
+ }
+
+ var outputPath = outputBox.Text?.Trim();
+ if (string.IsNullOrWhiteSpace(outputPath))
+ {
+ return;
+ }
+
+ if (Directory.Exists(outputPath))
+ {
+ OpenInExplorer(outputPath);
+ return;
+ }
+
+ if (File.Exists(outputPath))
+ {
+ OpenInExplorer(outputPath);
+ return;
+ }
+
+ var directory = Path.GetDirectoryName(outputPath);
+ OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? outputPath : directory);
+ }
+
+ private void OpenFaceDestinationFolder_Click(object? sender, RoutedEventArgs e)
+ {
+ var destBox = this.FindControl("FaceDestinationPathTextBox");
+ string? path = destBox?.Text?.Trim();
+ if (string.IsNullOrWhiteSpace(path) && DataContext is DataModel model)
+ {
+ path = (model.DestinationPath ?? string.Empty).Trim();
+ }
+
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ return;
+ }
+
+ if (Directory.Exists(path))
+ {
+ OpenInExplorer(path);
+ return;
+ }
+
+ var directory = Path.GetDirectoryName(path);
+ OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? path : directory);
+ }
+
+ 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}\"");
+ }
+ else if (Directory.Exists(normalizedPath))
+ {
+ Process.Start(new ProcessStartInfo { FileName = normalizedPath, UseShellExecute = true });
+ }
+ }
+ catch
+ {
+ // Ignore failures when opening Explorer.
+ }
+ }
}
diff --git a/imagecatalog/AvaloniaViews/GeneralTabView.axaml b/imagecatalog/AvaloniaViews/GeneralTabView.axaml
index ed0dd98..82979eb 100644
--- a/imagecatalog/AvaloniaViews/GeneralTabView.axaml
+++ b/imagecatalog/AvaloniaViews/GeneralTabView.axaml
@@ -1,24 +1,43 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/imagecatalog/AvaloniaViews/GeneralTabView.axaml.cs b/imagecatalog/AvaloniaViews/GeneralTabView.axaml.cs
index 9670c2a..cdcbec9 100644
--- a/imagecatalog/AvaloniaViews/GeneralTabView.axaml.cs
+++ b/imagecatalog/AvaloniaViews/GeneralTabView.axaml.cs
@@ -1,4 +1,9 @@
using Avalonia.Controls;
+using Avalonia.Interactivity;
+using System;
+using System.Diagnostics;
+using System.IO;
+
namespace ImageCatalog_2.AvaloniaViews;
public partial class GeneralTabView : Avalonia.Controls.UserControl
@@ -7,4 +12,45 @@ public partial class GeneralTabView : Avalonia.Controls.UserControl
{
InitializeComponent();
}
+
+ private void OpenSourceFolder_Click(object? sender, RoutedEventArgs e)
+ {
+ if (DataContext is DataModel model)
+ {
+ OpenInExplorer(model.SourcePath);
+ }
+ }
+
+ private void OpenDestinationFolder_Click(object? sender, RoutedEventArgs e)
+ {
+ if (DataContext is DataModel model)
+ {
+ OpenInExplorer(model.DestinationPath);
+ }
+ }
+
+ 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}\"");
+ }
+ else if (Directory.Exists(normalizedPath))
+ {
+ Process.Start(new ProcessStartInfo { FileName = normalizedPath, UseShellExecute = true });
+ }
+ }
+ catch
+ {
+ // Ignore failures when opening Explorer.
+ }
+ }
}
diff --git a/imagecatalog/AvaloniaViews/RaceUploadTabView.axaml b/imagecatalog/AvaloniaViews/RaceUploadTabView.axaml
index 1c9a6a8..cc3b507 100644
--- a/imagecatalog/AvaloniaViews/RaceUploadTabView.axaml
+++ b/imagecatalog/AvaloniaViews/RaceUploadTabView.axaml
@@ -7,7 +7,7 @@
-
+
@@ -16,7 +16,7 @@
-
+
@@ -49,7 +49,7 @@
-
+
diff --git a/imagecatalog/Controls/PathPickerField.axaml b/imagecatalog/Controls/PathPickerField.axaml
deleted file mode 100644
index dfa6948..0000000
--- a/imagecatalog/Controls/PathPickerField.axaml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/imagecatalog/Controls/PathPickerField.axaml.cs b/imagecatalog/Controls/PathPickerField.axaml.cs
deleted file mode 100644
index 6ec2538..0000000
--- a/imagecatalog/Controls/PathPickerField.axaml.cs
+++ /dev/null
@@ -1,285 +0,0 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Data;
-using Avalonia.Platform.Storage;
-using ImageCatalog_2.Services;
-using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-
-namespace ImageCatalog_2.Controls;
-
-public partial class PathPickerField : UserControl
-{
- private readonly PickerPreferenceService _pickerPreferenceService;
-
- public static readonly StyledProperty LabelProperty =
- AvaloniaProperty.Register(nameof(Label), string.Empty);
-
- public static readonly StyledProperty TextProperty =
- AvaloniaProperty.Register(nameof(Text), string.Empty, defaultBindingMode: BindingMode.TwoWay);
-
- public static readonly StyledProperty WatermarkProperty =
- AvaloniaProperty.Register(nameof(Watermark), string.Empty);
-
- public static readonly StyledProperty PreferenceKeyProperty =
- AvaloniaProperty.Register(nameof(PreferenceKey), string.Empty);
-
- public static readonly StyledProperty PickerTitleProperty =
- AvaloniaProperty.Register(nameof(PickerTitle), string.Empty);
-
- public static readonly StyledProperty FileTypeNameProperty =
- AvaloniaProperty.Register(nameof(FileTypeName), string.Empty);
-
- public static readonly StyledProperty FilePatternsProperty =
- AvaloniaProperty.Register(nameof(FilePatterns), string.Empty);
-
- public static readonly StyledProperty DefaultExtensionProperty =
- AvaloniaProperty.Register(nameof(DefaultExtension), string.Empty);
-
- public static readonly StyledProperty PickerModeProperty =
- AvaloniaProperty.Register(nameof(PickerMode), PathPickerSelectionMode.Folder);
-
- public static readonly StyledProperty IsTextReadOnlyProperty =
- AvaloniaProperty.Register(nameof(IsTextReadOnly), false);
-
- public static readonly StyledProperty ShowPickerButtonProperty =
- AvaloniaProperty.Register(nameof(ShowPickerButton), true);
-
- public static readonly StyledProperty AppendDirectorySeparatorProperty =
- AvaloniaProperty.Register(nameof(AppendDirectorySeparator), false);
-
- public PathPickerField()
- {
- _pickerPreferenceService = Program.ServiceProvider.GetRequiredService();
- InitializeComponent();
- }
-
- public string Label
- {
- get => GetValue(LabelProperty);
- set => SetValue(LabelProperty, value);
- }
-
- public string Text
- {
- get => GetValue(TextProperty);
- set => SetValue(TextProperty, value);
- }
-
- public string Watermark
- {
- get => GetValue(WatermarkProperty);
- set => SetValue(WatermarkProperty, value);
- }
-
- public string PreferenceKey
- {
- get => GetValue(PreferenceKeyProperty);
- set => SetValue(PreferenceKeyProperty, value);
- }
-
- public string PickerTitle
- {
- get => GetValue(PickerTitleProperty);
- set => SetValue(PickerTitleProperty, value);
- }
-
- public string FileTypeName
- {
- get => GetValue(FileTypeNameProperty);
- set => SetValue(FileTypeNameProperty, value);
- }
-
- public string FilePatterns
- {
- get => GetValue(FilePatternsProperty);
- set => SetValue(FilePatternsProperty, value);
- }
-
- public string DefaultExtension
- {
- get => GetValue(DefaultExtensionProperty);
- set => SetValue(DefaultExtensionProperty, value);
- }
-
- public PathPickerSelectionMode PickerMode
- {
- get => GetValue(PickerModeProperty);
- set => SetValue(PickerModeProperty, value);
- }
-
- public bool IsTextReadOnly
- {
- get => GetValue(IsTextReadOnlyProperty);
- set => SetValue(IsTextReadOnlyProperty, value);
- }
-
- public bool ShowPickerButton
- {
- get => GetValue(ShowPickerButtonProperty);
- set => SetValue(ShowPickerButtonProperty, value);
- }
-
- public bool AppendDirectorySeparator
- {
- get => GetValue(AppendDirectorySeparatorProperty);
- set => SetValue(AppendDirectorySeparatorProperty, value);
- }
-
- private async void PickPath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
- {
- var topLevel = TopLevel.GetTopLevel(this);
- var storageProvider = topLevel?.StorageProvider;
- if (storageProvider is null)
- {
- return;
- }
-
- var selectedPath = await PickPathAsync(storageProvider);
- if (string.IsNullOrWhiteSpace(selectedPath))
- {
- return;
- }
-
- Text = selectedPath;
-
- if (!string.IsNullOrWhiteSpace(PreferenceKey))
- {
- _pickerPreferenceService.RememberPath(PreferenceKey, selectedPath);
- }
- }
-
- private void OpenPath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
- {
- PathShellService.OpenInExplorer(Text);
- }
-
- private async Task PickPathAsync(IStorageProvider storageProvider)
- {
- var suggestedStartLocation = await TryGetSuggestedStartLocationAsync(storageProvider);
- var pickerTitle = ResolvePickerTitle();
-
- switch (PickerMode)
- {
- case PathPickerSelectionMode.Folder:
- {
- var folders = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
- {
- Title = pickerTitle,
- SuggestedStartLocation = suggestedStartLocation
- });
-
- return folders.Count == 0
- ? null
- : NormalizeSelectedPath(folders[0].Path.LocalPath);
- }
- case PathPickerSelectionMode.OpenFile:
- {
- var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
- {
- Title = pickerTitle,
- SuggestedStartLocation = suggestedStartLocation,
- FileTypeFilter = BuildFileTypes()
- });
-
- return files.Count == 0
- ? null
- : files[0].Path.LocalPath;
- }
- case PathPickerSelectionMode.SaveFile:
- {
- var file = await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
- {
- Title = pickerTitle,
- SuggestedStartLocation = suggestedStartLocation,
- DefaultExtension = NormalizeDefaultExtension(DefaultExtension),
- FileTypeChoices = BuildFileTypes()
- });
-
- return file?.Path.LocalPath;
- }
- default:
- return null;
- }
- }
-
- private async Task TryGetSuggestedStartLocationAsync(IStorageProvider storageProvider)
- {
- if (string.IsNullOrWhiteSpace(PreferenceKey))
- {
- return null;
- }
-
- return await _pickerPreferenceService.TryGetStartFolderAsync(storageProvider, PreferenceKey, Text);
- }
-
- private IReadOnlyList BuildFileTypes()
- {
- var patterns = (FilePatterns ?? string.Empty)
- .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
-
- if (patterns.Length == 0)
- {
- return Array.Empty();
- }
-
- var fileTypeName = string.IsNullOrWhiteSpace(FileTypeName)
- ? "File"
- : FileTypeName.Trim();
-
- return
- [
- new FilePickerFileType(fileTypeName)
- {
- Patterns = patterns.ToList()
- }
- ];
- }
-
- private string ResolvePickerTitle()
- {
- if (!string.IsNullOrWhiteSpace(PickerTitle))
- {
- return PickerTitle;
- }
-
- var cleanedLabel = string.IsNullOrWhiteSpace(Label)
- ? "percorso"
- : Label.Trim().TrimEnd(':');
-
- return PickerMode == PathPickerSelectionMode.SaveFile
- ? $"Salva {cleanedLabel.ToLowerInvariant()}"
- : $"Seleziona {cleanedLabel.ToLowerInvariant()}";
- }
-
- private string NormalizeSelectedPath(string selectedPath)
- {
- if (PickerMode != PathPickerSelectionMode.Folder || !AppendDirectorySeparator)
- {
- return selectedPath;
- }
-
- if (string.IsNullOrWhiteSpace(selectedPath))
- {
- return string.Empty;
- }
-
- return selectedPath.EndsWith(Path.DirectorySeparatorChar)
- || selectedPath.EndsWith(Path.AltDirectorySeparatorChar)
- ? selectedPath
- : selectedPath + Path.DirectorySeparatorChar;
- }
-
- private static string NormalizeDefaultExtension(string extension)
- {
- if (string.IsNullOrWhiteSpace(extension))
- {
- return string.Empty;
- }
-
- return extension.Trim().TrimStart('.');
- }
-}
\ No newline at end of file
diff --git a/imagecatalog/Controls/PathPickerSelectionMode.cs b/imagecatalog/Controls/PathPickerSelectionMode.cs
deleted file mode 100644
index 27d456e..0000000
--- a/imagecatalog/Controls/PathPickerSelectionMode.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace ImageCatalog_2.Controls;
-
-public enum PathPickerSelectionMode
-{
- Folder,
- OpenFile,
- SaveFile
-}
\ No newline at end of file
diff --git a/imagecatalog/Converters/FilePathToBitmapConverter.cs b/imagecatalog/Converters/FilePathToBitmapConverter.cs
deleted file mode 100644
index 614897d..0000000
--- a/imagecatalog/Converters/FilePathToBitmapConverter.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using System.Globalization;
-using Avalonia.Data.Converters;
-using Avalonia.Media.Imaging;
-
-namespace ImageCatalog_2.Converters;
-
-public sealed class FilePathToBitmapConverter : IValueConverter
-{
- public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- if (value is not string path || string.IsNullOrWhiteSpace(path))
- {
- return null;
- }
-
- try
- {
- return new Bitmap(path);
- }
- catch
- {
- return null;
- }
- }
-
- public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- throw new NotSupportedException();
- }
-}
diff --git a/imagecatalog/DataModel.cs b/imagecatalog/DataModel.cs
index d4a3dc4..66ce8d4 100644
--- a/imagecatalog/DataModel.cs
+++ b/imagecatalog/DataModel.cs
@@ -13,16 +13,13 @@ using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
-using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using AIFotoONLUS.Core;
-using System.Text.RegularExpressions;
using AutoMapper;
using MaddoShared;
using Microsoft.Extensions.Logging;
-using System.Collections.ObjectModel;
namespace ImageCatalog_2
{
@@ -44,8 +41,6 @@ namespace ImageCatalog_2
public ICommand StartAiCommand { get; }
public ICommand StartFaceEncoderCommand { get; }
public ICommand StopFaceEncoderCommand { get; }
- public ICommand StartFaceMatcherCommand { get; }
- public ICommand StopFaceMatcherCommand { get; }
private readonly ITestService _service;
private readonly ILogger _logger;
@@ -62,28 +57,13 @@ namespace ImageCatalog_2
private readonly IMapper _mapper;
private readonly AsyncCommand _startFaceEncoderCommand;
private readonly AsyncCommand _stopFaceEncoderCommand;
- private readonly AsyncCommand _startFaceMatcherCommand;
- private readonly AsyncCommand _stopFaceMatcherCommand;
private readonly object _faceEncoderProcessLock = new();
- private readonly object _faceMatcherProcessLock = new();
private Process? _faceEncoderProcess;
- private Process? _faceMatcherProcess;
private CancellationTokenSource? _faceEncoderWatcherTokenSource;
private Task? _faceEncoderWatcherTask;
private CancellationTokenSource? _faceEncoderLogWatcherTokenSource;
private Task? _faceEncoderLogWatcherTask;
- private CancellationTokenSource? _faceMatcherWatcherTokenSource;
- private Task? _faceMatcherWatcherTask;
- private CancellationTokenSource? _faceMatcherLogWatcherTokenSource;
- private Task? _faceMatcherLogWatcherTask;
private bool _hasStartedFaceEncoderInSession;
- private bool _hasStartedFaceMatcherInSession;
- 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";
// ComboBox collections
public List AvailableFonts { get; }
@@ -125,12 +105,8 @@ namespace ImageCatalog_2
StartAiCommand = new AsyncCommand(StartAiAsync);
_startFaceEncoderCommand = new AsyncCommand(RunFaceEncoderAsync, CanRunFaceEncoder);
_stopFaceEncoderCommand = new AsyncCommand(() => StopFaceEncoderAsync("Arresto richiesto dall'utente."), CanStopFaceEncoder);
- _startFaceMatcherCommand = new AsyncCommand(RunFaceMatcherAsync, CanRunFaceMatcher);
- _stopFaceMatcherCommand = new AsyncCommand(() => StopFaceMatcherAsync("Arresto richiesto dall'utente."), CanStopFaceMatcher);
StartFaceEncoderCommand = _startFaceEncoderCommand;
StopFaceEncoderCommand = _stopFaceEncoderCommand;
- StartFaceMatcherCommand = _startFaceMatcherCommand;
- StopFaceMatcherCommand = _stopFaceMatcherCommand;
SelectSourceFolderCommand = new RelayCommand(SelectSourceFolder);
SelectDestinationFolderCommand = new RelayCommand(SelectDestinationFolder);
@@ -142,18 +118,12 @@ namespace ImageCatalog_2
// Load available fonts
AvailableFonts = LoadAvailableFonts();
- QueueRefreshNumberAiGpuCapabilities();
+ RefreshNumberAiGpuCapabilities();
RefreshFaceExecutableCapabilities();
}
private async Task StartAiAsync()
{
- if (!await ConfirmAiCsvOverwriteIfNeededAsync().ConfigureAwait(false))
- {
- await InvokeOnUiThreadAsync(() => NumberAiStatsSummary = "OCR annullato.").ConfigureAwait(false);
- return;
- }
-
MainToken = new CancellationTokenSource();
try
{
@@ -168,7 +138,7 @@ namespace ImageCatalog_2
_logger.LogError(ex, "AI extraction failed");
if (UseNumberAiGpu)
{
- QueueRefreshNumberAiGpuCapabilities();
+ RefreshNumberAiGpuCapabilities();
}
await InvokeOnUiThreadAsync(() => NumberAiStatsSummary = $"Errore OCR: {ex.GetBaseException().Message}").ConfigureAwait(false);
@@ -266,7 +236,7 @@ namespace ImageCatalog_2
set
{
_ai.ModelsFolderPath = value;
- QueueRefreshNumberAiGpuCapabilities();
+ RefreshNumberAiGpuCapabilities();
}
}
@@ -276,8 +246,6 @@ namespace ImageCatalog_2
set => _ai.CsvOutputPath = value;
}
- public Func>? ConfirmAiCsvOverwriteAsync { get; set; }
-
public bool UseNumberAiGpu
{
get => _ai.UseNumberAiGpu;
@@ -391,62 +359,6 @@ namespace ImageCatalog_2
private set => _ai.FaceCommandOutput = value;
}
- public string FaceMatcherExecutablePath
- {
- get => _ai.FaceMatcherExecutablePath;
- set => _ai.FaceMatcherExecutablePath = value ?? string.Empty;
- }
-
- public string FaceMatcherEncodingsPath
- {
- get => _ai.FaceMatcherEncodingsPath;
- set => _ai.FaceMatcherEncodingsPath = value ?? string.Empty;
- }
-
- public string FaceMatcherOutputPath
- {
- get => _ai.FaceMatcherOutputPath;
- set => _ai.FaceMatcherOutputPath = value ?? string.Empty;
- }
-
- public string FaceMatcherLogPath
- {
- get => _ai.FaceMatcherLogPath;
- set => _ai.FaceMatcherLogPath = value ?? string.Empty;
- }
-
- public double FaceMatcherTolerance
- {
- get => _ai.FaceMatcherTolerance;
- set => _ai.FaceMatcherTolerance = NormalizeFaceMatcherTolerance(value);
- }
-
- public string FaceMatcherSelectedImagePath
- {
- get => _ai.FaceMatcherSelectedImagePath;
- set => _ai.FaceMatcherSelectedImagePath = value ?? string.Empty;
- }
-
- public bool IsFaceMatcherRunning
- {
- get => _ai.IsFaceMatcherRunning;
- private set => _ai.IsFaceMatcherRunning = value;
- }
-
- public string FaceMatcherStatusMessage
- {
- get => _ai.FaceMatcherStatusMessage;
- private set => _ai.FaceMatcherStatusMessage = value;
- }
-
- public string FaceMatcherCommandOutput
- {
- get => _ai.FaceMatcherCommandOutput;
- private set => _ai.FaceMatcherCommandOutput = value;
- }
-
- public System.Collections.ObjectModel.ObservableCollection FaceMatcherResults => _ai.FaceMatcherResults;
-
// Race upload settings
public string ApiLogin
{
@@ -652,11 +564,6 @@ namespace ImageCatalog_2
return;
}
- if (string.Equals(e.PropertyName, nameof(PathSettingsViewModel.DestinationPath), StringComparison.Ordinal))
- {
- UpdateAiCsvOutputPathForDestination();
- }
-
NotifyPropertyChanged(e.PropertyName);
}
@@ -669,7 +576,6 @@ namespace ImageCatalog_2
NotifyPropertyChanged(e.PropertyName);
UpdateFaceEncoderCommandStates();
- UpdateFaceMatcherCommandStates();
}
private void OnRaceUploadPropertyChanged(object? sender, PropertyChangedEventArgs e)
@@ -1622,12 +1528,6 @@ namespace ImageCatalog_2
_stopFaceEncoderCommand?.RaiseCanExecuteChanged();
}
- private void UpdateFaceMatcherCommandStates()
- {
- _startFaceMatcherCommand?.RaiseCanExecuteChanged();
- _stopFaceMatcherCommand?.RaiseCanExecuteChanged();
- }
-
private async Task RunFaceEncoderAsync()
{
if (IsFaceEncoderRunning)
@@ -1793,252 +1693,6 @@ namespace ImageCatalog_2
}
}
- private bool CanRunFaceMatcher()
- {
- return !IsFaceMatcherRunning;
- }
-
- private bool CanStopFaceMatcher()
- {
- return IsFaceMatcherRunning;
- }
-
- private async Task RunFaceMatcherAsync()
- {
- if (IsFaceMatcherRunning)
- {
- FaceMatcherStatusMessage = "Face matcher gia in esecuzione.";
- return;
- }
-
- var executablePath = ResolveConfiguredFaceMatcherExecutablePath(NormalizeFilePathArgument(FaceMatcherExecutablePath), NormalizeFilePathArgument(FaceExecutablePath));
- if (string.IsNullOrWhiteSpace(executablePath) || !File.Exists(executablePath))
- {
- FaceMatcherStatusMessage = "Percorso face_matcher.exe non valido.";
- return;
- }
-
- var searchImagePath = NormalizeFilePathArgument(FaceMatcherSelectedImagePath);
- if (string.IsNullOrWhiteSpace(searchImagePath) || !File.Exists(searchImagePath))
- {
- FaceMatcherStatusMessage = "Seleziona un'immagine valida per il match.";
- return;
- }
-
- var encodingsPath = ResolveConfiguredFaceMatcherEncodingsPath(NormalizeFilePathArgument(FaceMatcherEncodingsPath), NormalizeDirectoryPathArgument(FaceOutputFolderPath));
- if (string.IsNullOrWhiteSpace(encodingsPath) || !File.Exists(encodingsPath))
- {
- FaceMatcherStatusMessage = "File encodings .pkl non trovato.";
- return;
- }
-
- var fallbackOutputRoot = ResolveFaceMatcherFallbackOutputRoot(NormalizeFilePathArgument(FaceMatcherOutputPath), NormalizeFilePathArgument(FaceMatcherLogPath), NormalizeDirectoryPathArgument(FaceOutputFolderPath), executablePath);
- var outputPaths = ResolveFaceMatcherOutputPaths(FaceMatcherOutputPath, FaceMatcherLogPath, fallbackOutputRoot, searchImagePath, DateTime.Now);
-
- try
- {
- Directory.CreateDirectory(Path.GetDirectoryName(outputPaths.CsvPath) ?? fallbackOutputRoot);
- Directory.CreateDirectory(Path.GetDirectoryName(outputPaths.LogPath) ?? fallbackOutputRoot);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Unable to create face matcher output directory.");
- FaceMatcherStatusMessage = "Impossibile creare cartelle output/log del matcher.";
- return;
- }
-
- var tolerance = NormalizeFaceMatcherTolerance(FaceMatcherTolerance);
- FaceMatcherExecutablePath = executablePath;
- FaceMatcherEncodingsPath = encodingsPath;
- FaceMatcherOutputPath = outputPaths.CsvPath;
- FaceMatcherLogPath = outputPaths.LogPath;
-
- await InvokeOnUiThreadAsync(() =>
- {
- FaceMatcherResults.Clear();
- FaceMatcherCommandOutput = string.Empty;
- FaceMatcherStatusMessage = "Esecuzione face matcher in corso...";
- }).ConfigureAwait(false);
-
- var transcriptLines = new StringBuilder();
- var outputLines = new StringBuilder();
- var errorLines = new StringBuilder();
-
- try
- {
- var processStartInfo = new ProcessStartInfo
- {
- FileName = executablePath,
- WorkingDirectory = Path.GetDirectoryName(executablePath) ?? Environment.CurrentDirectory,
- UseShellExecute = false,
- RedirectStandardOutput = false,
- RedirectStandardError = false,
- RedirectStandardInput = false,
- CreateNoWindow = true,
- };
-
- processStartInfo.Environment["PYTHONUTF8"] = "1";
- processStartInfo.Environment["PYTHONIOENCODING"] = "utf-8";
-
- processStartInfo.ArgumentList.Add("--image");
- processStartInfo.ArgumentList.Add(searchImagePath);
- processStartInfo.ArgumentList.Add("--encodings");
- processStartInfo.ArgumentList.Add(encodingsPath);
- processStartInfo.ArgumentList.Add("--out");
- processStartInfo.ArgumentList.Add(outputPaths.CsvPath);
- processStartInfo.ArgumentList.Add("--log");
- processStartInfo.ArgumentList.Add(outputPaths.LogPath);
- processStartInfo.ArgumentList.Add("--tolerance");
- processStartInfo.ArgumentList.Add(tolerance.ToString("0.##", CultureInfo.InvariantCulture));
-
- using var process = new Process { StartInfo = processStartInfo, EnableRaisingEvents = true };
- process.Exited += (_, _) =>
- {
- _ = InvokeOnUiThreadAsync(() =>
- {
- if (!ComputeIsFaceMatcherRunning())
- {
- IsFaceMatcherRunning = false;
- }
- });
- };
-
- if (!process.Start())
- {
- throw new InvalidOperationException("Avvio face matcher fallito.");
- }
-
- _hasStartedFaceMatcherInSession = true;
- EnsureFaceMatcherWatcherStarted();
- TrackFaceMatcherProcess(process);
- await InvokeOnUiThreadAsync(() => IsFaceMatcherRunning = true).ConfigureAwait(false);
-
- StartFaceMatcherLogWatcher(outputPaths.LogPath, outputLines, transcriptLines);
- await process.WaitForExitAsync().ConfigureAwait(false);
-
- var isNoFacesRun = process.ExitCode == 1 && await LogIndicatesNoFacesAsync(outputPaths.LogPath).ConfigureAwait(false);
- if (process.ExitCode == 0 || isNoFacesRun)
- {
- var parsedRows = await ParseFaceMatcherCsvAsync(outputPaths.CsvPath, outputPaths.LogPath).ConfigureAwait(false);
- var resolvedPaths = ResolveDestinationImagesByFileName(DestinationPath, parsedRows.Select(row => row.PhotoId));
- await InvokeOnUiThreadAsync(() =>
- {
- FaceMatcherResults.Clear();
- foreach (var row in parsedRows)
- {
- resolvedPaths.TryGetValue(row.PhotoId, out var candidates);
- candidates ??= [];
-
- FaceMatcherResults.Add(new FaceMatcherResultItem
- {
- PhotoId = row.PhotoId,
- Score = row.Score,
- ResolvedImagePath = candidates.FirstOrDefault() ?? string.Empty,
- CandidateCount = candidates.Count,
- RawRow = row.RawRow,
- DebugSummary = row.DebugSummary,
- SearchImagePath = searchImagePath,
- CsvPath = outputPaths.CsvPath,
- LogPath = outputPaths.LogPath
- });
- }
- }).ConfigureAwait(false);
- }
-
- var summary = BuildFaceMatcherSummary(process.ExitCode, processStartInfo, outputPaths.CsvPath, outputPaths.LogPath, outputLines, errorLines, FaceMatcherResults.Count);
- await InvokeOnUiThreadAsync(() =>
- {
- FaceMatcherCommandOutput = string.IsNullOrWhiteSpace(FaceMatcherCommandOutput)
- ? summary
- : $"{FaceMatcherCommandOutput.TrimEnd()}\n\n{summary}";
- FaceMatcherStatusMessage = process.ExitCode switch
- {
- 0 when FaceMatcherResults.Count > 0 => $"Face matcher completato: {FaceMatcherResults.Count} match.",
- 0 => "Face matcher completato senza match.",
- 1 when isNoFacesRun => "Face matcher completato: nessun volto rilevato nell'immagine di ricerca.",
- _ => $"Face matcher terminato con errore (code {process.ExitCode})."
- };
- }).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Console.Error.WriteLine(ex);
- _logger.LogError(ex, "Face matcher execution failed.");
- await InvokeOnUiThreadAsync(() =>
- {
- FaceMatcherCommandOutput = ex.ToString();
- FaceMatcherStatusMessage = "Errore durante esecuzione face matcher.";
- }).ConfigureAwait(false);
- }
- finally
- {
- await StopFaceMatcherLogWatcherAsync().ConfigureAwait(false);
- ClearTrackedFaceMatcherProcess();
- await InvokeOnUiThreadAsync(() => IsFaceMatcherRunning = ComputeIsFaceMatcherRunning()).ConfigureAwait(false);
- }
- }
-
- public async Task StopFaceMatcherAsync(string reason, bool waitForExit = true)
- {
- var trackedProcess = GetTrackedFaceMatcherProcess();
- Process? process = null;
-
- if (trackedProcess is not null)
- {
- process = trackedProcess;
- }
- else if (_hasStartedFaceMatcherInSession)
- {
- process = FindConfiguredFaceMatcherProcess();
- }
-
- if (process is null)
- {
- await StopFaceMatcherLogWatcherAsync().ConfigureAwait(false);
- await InvokeOnUiThreadAsync(() =>
- {
- IsFaceMatcherRunning = false;
- FaceMatcherStatusMessage = "Face matcher non in esecuzione.";
- }).ConfigureAwait(false);
- return;
- }
-
- using (process)
- {
- var gracefulStopRequested = TryRequestFaceMatcherStop(process);
- var exited = !IsProcessAlive(process);
-
- if (!exited && waitForExit)
- {
- exited = await WaitForProcessExitAsync(process, TimeSpan.FromSeconds(5)).ConfigureAwait(false);
- }
-
- if (!exited)
- {
- try
- {
- process.Kill(entireProcessTree: true);
- }
- catch (Exception ex)
- {
- _logger.LogWarning(ex, "Unable to terminate face matcher process {ProcessId}", process.Id);
- }
- }
-
- await StopFaceMatcherLogWatcherAsync().ConfigureAwait(false);
- ClearTrackedFaceMatcherProcess();
- await InvokeOnUiThreadAsync(() =>
- {
- IsFaceMatcherRunning = !exited && IsProcessAlive(process);
- FaceMatcherStatusMessage = exited
- ? "Face matcher arrestato."
- : gracefulStopRequested
- ? "Segnale di arresto inviato al face matcher."
- : "Arresto forzato del face matcher richiesto.";
- }).ConfigureAwait(false);
- }
- }
-
private async Task WatchFaceEncoderProcessAsync(CancellationToken token)
{
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
@@ -2065,32 +1719,6 @@ namespace ImageCatalog_2
}
}
- private async Task WatchFaceMatcherProcessAsync(CancellationToken token)
- {
- using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
-
- try
- {
- while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
- {
- if (!_hasStartedFaceMatcherInSession)
- {
- continue;
- }
-
- var isRunning = ComputeIsFaceMatcherRunning();
- if (isRunning != IsFaceMatcherRunning)
- {
- await InvokeOnUiThreadAsync(() => IsFaceMatcherRunning = isRunning).ConfigureAwait(false);
- }
- }
- }
- catch (OperationCanceledException)
- {
- // App shutdown.
- }
- }
-
private void EnsureFaceEncoderWatcherStarted()
{
if (_faceEncoderWatcherTask is not null)
@@ -2102,17 +1730,6 @@ namespace ImageCatalog_2
_faceEncoderWatcherTask = WatchFaceEncoderProcessAsync(_faceEncoderWatcherTokenSource.Token);
}
- private void EnsureFaceMatcherWatcherStarted()
- {
- if (_faceMatcherWatcherTask is not null)
- {
- return;
- }
-
- _faceMatcherWatcherTokenSource = new CancellationTokenSource();
- _faceMatcherWatcherTask = WatchFaceMatcherProcessAsync(_faceMatcherWatcherTokenSource.Token);
- }
-
private void StartFaceEncoderLogWatcher(string logFilePath, StringBuilder outputLines, StringBuilder transcriptLines)
{
_faceEncoderLogWatcherTokenSource?.Cancel();
@@ -2122,15 +1739,6 @@ namespace ImageCatalog_2
_faceEncoderLogWatcherTask = WatchFaceEncoderLogFileAsync(logFilePath, outputLines, transcriptLines, _faceEncoderLogWatcherTokenSource.Token);
}
- private void StartFaceMatcherLogWatcher(string logFilePath, StringBuilder outputLines, StringBuilder transcriptLines)
- {
- _faceMatcherLogWatcherTokenSource?.Cancel();
- _faceMatcherLogWatcherTokenSource?.Dispose();
-
- _faceMatcherLogWatcherTokenSource = new CancellationTokenSource();
- _faceMatcherLogWatcherTask = WatchFaceMatcherLogFileAsync(logFilePath, outputLines, transcriptLines, _faceMatcherLogWatcherTokenSource.Token);
- }
-
private async Task StopFaceEncoderLogWatcherAsync()
{
var tokenSource = _faceEncoderLogWatcherTokenSource;
@@ -2162,37 +1770,6 @@ namespace ImageCatalog_2
}
}
- private async Task StopFaceMatcherLogWatcherAsync()
- {
- var tokenSource = _faceMatcherLogWatcherTokenSource;
- var task = _faceMatcherLogWatcherTask;
-
- _faceMatcherLogWatcherTokenSource = null;
- _faceMatcherLogWatcherTask = null;
-
- if (tokenSource is null)
- {
- return;
- }
-
- try
- {
- await tokenSource.CancelAsync().ConfigureAwait(false);
- if (task is not null)
- {
- await task.ConfigureAwait(false);
- }
- }
- catch (OperationCanceledException)
- {
- // Expected when shutting down the watcher.
- }
- finally
- {
- tokenSource.Dispose();
- }
- }
-
private async Task WatchFaceEncoderLogFileAsync(string logFilePath, StringBuilder outputLines, StringBuilder transcriptLines, CancellationToken token)
{
long filePosition = 0;
@@ -2237,50 +1814,6 @@ namespace ImageCatalog_2
}
}
- private async Task WatchFaceMatcherLogFileAsync(string logFilePath, StringBuilder outputLines, StringBuilder transcriptLines, CancellationToken token)
- {
- long filePosition = 0;
-
- while (!token.IsCancellationRequested)
- {
- try
- {
- if (File.Exists(logFilePath))
- {
- using var stream = new FileStream(logFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
- if (filePosition > stream.Length)
- {
- filePosition = 0;
- }
-
- stream.Seek(filePosition, SeekOrigin.Begin);
- using var reader = new StreamReader(stream);
- while (!reader.EndOfStream)
- {
- var line = await reader.ReadLineAsync(token).ConfigureAwait(false);
- AppendFaceMatcherProcessOutput(outputLines, transcriptLines, line, isError: false);
- }
-
- filePosition = stream.Position;
- }
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (IOException)
- {
- // Retry while the matcher is still writing.
- }
- catch (UnauthorizedAccessException)
- {
- // Retry if the file is transiently locked.
- }
-
- await Task.Delay(TimeSpan.FromMilliseconds(250), token).ConfigureAwait(false);
- }
- }
-
private void RefreshFaceExecutableCapabilities()
{
var executableRoot = NormalizeFilePathArgument(_ai.FaceExecutablePath);
@@ -2299,86 +1832,31 @@ namespace ImageCatalog_2
}
}
- private void QueueRefreshNumberAiGpuCapabilities()
+ private void RefreshNumberAiGpuCapabilities()
{
if (!TryBuildNumberAiModelConfiguration(out var configuration))
{
- _numberAiGpuValidationPending = false;
NumberAiGpuOptionEnabled = false;
_ai.UseNumberAiGpu = false;
return;
}
- _numberAiGpuValidationPending = true;
- var requestVersion = Interlocked.Increment(ref _numberAiGpuRefreshVersion);
- _ = RefreshNumberAiGpuCapabilitiesAsync(configuration, requestVersion);
- }
-
- private async Task RefreshNumberAiGpuCapabilitiesAsync(ModelConfiguration configuration, int requestVersion)
- {
- try
+ NumberAiGpuOptionEnabled = NumberRecognitionEngine.TryValidateGpuRuntime(configuration, _logger, out _);
+ if (!NumberAiGpuOptionEnabled)
{
- var gpuAvailable = await Task.Run(() =>
- NumberRecognitionEngine.TryValidateGpuRuntime(configuration, _logger, out _)).ConfigureAwait(false);
-
- if (requestVersion != Volatile.Read(ref _numberAiGpuRefreshVersion))
- {
- return;
- }
-
- await InvokeOnUiThreadAsync(() =>
- {
- if (requestVersion != Volatile.Read(ref _numberAiGpuRefreshVersion))
- {
- return;
- }
-
- _numberAiGpuValidationPending = false;
- NumberAiGpuOptionEnabled = gpuAvailable;
- if (!gpuAvailable)
- {
- _ai.UseNumberAiGpu = false;
- }
- }).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- if (requestVersion != Volatile.Read(ref _numberAiGpuRefreshVersion))
- {
- return;
- }
-
- _logger.LogWarning(ex, "Failed to refresh OCR GPU capabilities.");
-
- await InvokeOnUiThreadAsync(() =>
- {
- if (requestVersion != Volatile.Read(ref _numberAiGpuRefreshVersion))
- {
- return;
- }
-
- _numberAiGpuValidationPending = false;
- NumberAiGpuOptionEnabled = false;
- _ai.UseNumberAiGpu = false;
- }).ConfigureAwait(false);
+ _ai.UseNumberAiGpu = false;
}
}
private void SetUseNumberAiGpu(bool value)
{
- if (!value)
+ if (!NumberAiGpuOptionEnabled)
{
_ai.UseNumberAiGpu = false;
return;
}
- if (NumberAiGpuOptionEnabled || _numberAiGpuValidationPending)
- {
- _ai.UseNumberAiGpu = true;
- return;
- }
-
- _ai.UseNumberAiGpu = false;
+ _ai.UseNumberAiGpu = value;
}
private bool TryBuildNumberAiModelConfiguration(out ModelConfiguration configuration)
@@ -2419,103 +1897,6 @@ namespace ImageCatalog_2
});
}
- internal async Task ConfirmAiCsvOverwriteIfNeededAsync()
- {
- var csvOutputPath = NormalizeFilePathArgument(CsvOutputPath);
- if (string.IsNullOrWhiteSpace(csvOutputPath) || !File.Exists(csvOutputPath))
- {
- return true;
- }
-
- var confirmOverwrite = ConfirmAiCsvOverwriteAsync;
- if (confirmOverwrite is null)
- {
- return true;
- }
-
- var message = $"Il file CSV esiste gia:\n{csvOutputPath}\n\nVuoi sovrascriverlo? Se scegli Annulla l'operazione OCR non verra avviata.";
- return await confirmOverwrite(AiCsvOverwriteDialogTitle, message).ConfigureAwait(false);
- }
-
- internal void UpdateAiCsvOutputPathForDestination()
- {
- var updatedPath = BuildAiCsvOutputPathForDestination(CsvOutputPath, DestinationPath);
- if (string.Equals(updatedPath, CsvOutputPath, StringComparison.Ordinal))
- {
- return;
- }
-
- CsvOutputPath = updatedPath;
- }
-
- internal static string BuildAiCsvOutputPathForDestination(string currentCsvOutputPath, string destinationPath)
- {
- var normalizedCsvPath = NormalizeFilePathArgument(currentCsvOutputPath);
- if (string.IsNullOrWhiteSpace(normalizedCsvPath))
- {
- return currentCsvOutputPath;
- }
-
- var directory = Path.GetDirectoryName(normalizedCsvPath);
- var destinationFolderName = GetDestinationFolderName(destinationPath);
- if (string.IsNullOrWhiteSpace(destinationFolderName))
- {
- return normalizedCsvPath;
- }
-
- var extension = Path.GetExtension(normalizedCsvPath);
- if (string.IsNullOrWhiteSpace(extension))
- {
- extension = ".csv";
- }
-
- var safeFileName = SanitizeFileName(destinationFolderName) + extension;
- return string.IsNullOrWhiteSpace(directory)
- ? safeFileName
- : Path.Combine(directory, safeFileName);
- }
-
- private static string GetDestinationFolderName(string destinationPath)
- {
- var normalizedDestinationPath = NormalizeDirectoryPathArgument(destinationPath);
- if (string.IsNullOrWhiteSpace(normalizedDestinationPath))
- {
- return string.Empty;
- }
-
- var trimmedPath = normalizedDestinationPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
- if (string.IsNullOrWhiteSpace(trimmedPath))
- {
- return string.Empty;
- }
-
- var rootPath = Path.GetPathRoot(trimmedPath)?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
- if (!string.IsNullOrWhiteSpace(rootPath)
- && string.Equals(trimmedPath, rootPath, StringComparison.OrdinalIgnoreCase))
- {
- return string.Empty;
- }
-
- return Path.GetFileName(trimmedPath);
- }
-
- private static string SanitizeFileName(string value)
- {
- if (string.IsNullOrWhiteSpace(value))
- {
- return string.Empty;
- }
-
- var invalidFileNameChars = Path.GetInvalidFileNameChars();
- var builder = new StringBuilder(value.Length);
- foreach (var character in value)
- {
- builder.Append(Array.IndexOf(invalidFileNameChars, character) >= 0 ? '_' : character);
- }
-
- return builder.ToString();
- }
-
private void SetUseFaceGpu(bool value)
{
var currentValue = _ai.UseFaceGpu;
@@ -2546,14 +1927,6 @@ namespace ImageCatalog_2
}
}
- private void TrackFaceMatcherProcess(Process process)
- {
- lock (_faceMatcherProcessLock)
- {
- _faceMatcherProcess = process;
- }
- }
-
private void ClearTrackedFaceEncoderProcess()
{
lock (_faceEncoderProcessLock)
@@ -2568,20 +1941,6 @@ namespace ImageCatalog_2
}
}
- private void ClearTrackedFaceMatcherProcess()
- {
- lock (_faceMatcherProcessLock)
- {
- if (_faceMatcherProcess is not null && _faceMatcherProcess.HasExited)
- {
- _faceMatcherProcess = null;
- return;
- }
-
- _faceMatcherProcess = null;
- }
- }
-
private Process? GetTrackedFaceEncoderProcess()
{
lock (_faceEncoderProcessLock)
@@ -2601,25 +1960,6 @@ namespace ImageCatalog_2
}
}
- private Process? GetTrackedFaceMatcherProcess()
- {
- lock (_faceMatcherProcessLock)
- {
- if (_faceMatcherProcess is null)
- {
- return null;
- }
-
- if (_faceMatcherProcess.HasExited)
- {
- _faceMatcherProcess = null;
- return null;
- }
-
- return _faceMatcherProcess;
- }
- }
-
private Process? FindConfiguredFaceEncoderProcess()
{
var configuredExecutablePath = ResolveConfiguredFaceEncoderExecutablePath(FaceExecutablePath, UseFaceGpu);
@@ -2648,34 +1988,6 @@ namespace ImageCatalog_2
return null;
}
- private Process? FindConfiguredFaceMatcherProcess()
- {
- var configuredExecutablePath = ResolveConfiguredFaceMatcherExecutablePath(FaceMatcherExecutablePath, FaceExecutablePath);
- if (string.IsNullOrWhiteSpace(configuredExecutablePath))
- {
- return null;
- }
-
- var processName = Path.GetFileNameWithoutExtension(configuredExecutablePath);
- foreach (var process in Process.GetProcessesByName(processName))
- {
- if (!IsProcessAlive(process))
- {
- process.Dispose();
- continue;
- }
-
- if (IsMatchingProcessPath(process, configuredExecutablePath))
- {
- return process;
- }
-
- process.Dispose();
- }
-
- return null;
- }
-
private bool ComputeIsFaceEncoderRunning()
{
var trackedProcess = GetTrackedFaceEncoderProcess();
@@ -2693,23 +2005,6 @@ namespace ImageCatalog_2
return discoveredProcess is not null;
}
- private bool ComputeIsFaceMatcherRunning()
- {
- var trackedProcess = GetTrackedFaceMatcherProcess();
- if (trackedProcess is not null)
- {
- return true;
- }
-
- if (!_hasStartedFaceMatcherInSession)
- {
- return false;
- }
-
- using var discoveredProcess = FindConfiguredFaceMatcherProcess();
- return discoveredProcess is not null;
- }
-
private static bool IsProcessAlive(Process process)
{
try
@@ -2761,31 +2056,6 @@ namespace ImageCatalog_2
}
}
- private bool TryRequestFaceMatcherStop(Process process)
- {
- if (!IsProcessAlive(process))
- {
- return true;
- }
-
-#if WINDOWS
- if (Program.TrySendConsoleInterrupt(process.Id))
- {
- return true;
- }
-#endif
-
- try
- {
- return process.CloseMainWindow();
- }
- catch (Exception ex)
- {
- _logger.LogWarning(ex, "Unable to request graceful stop for face matcher process {ProcessId}", process.Id);
- return false;
- }
- }
-
private static async Task WaitForProcessExitAsync(Process process, TimeSpan timeout)
{
if (!IsProcessAlive(process))
@@ -2841,88 +2111,6 @@ namespace ImageCatalog_2
_ = InvokeOnUiThreadAsync(() => FaceCommandOutput = transcript);
}
- private void AppendFaceMatcherProcessOutput(StringBuilder builder, StringBuilder transcriptBuilder, string? line, bool isError)
- {
- if (string.IsNullOrWhiteSpace(line))
- {
- return;
- }
-
- lock (builder)
- {
- builder.AppendLine(line);
- }
-
- if (isError)
- {
- Console.Error.WriteLine(line);
- }
- else
- {
- Console.WriteLine(line);
- }
-
- string transcript;
- lock (transcriptBuilder)
- {
- if (isError)
- {
- transcriptBuilder.Append("[stderr] ");
- }
-
- transcriptBuilder.AppendLine(line);
- transcript = transcriptBuilder.ToString();
- }
-
- _ = InvokeOnUiThreadAsync(() => FaceMatcherCommandOutput = transcript);
- }
-
- internal static string? ResolveConfiguredFaceMatcherExecutablePath(string configuredPath, string fallbackEncoderPath)
- {
- foreach (var candidate in EnumerateFaceMatcherExecutableCandidates(configuredPath, fallbackEncoderPath))
- {
- if (File.Exists(candidate))
- {
- return candidate;
- }
- }
-
- return null;
- }
-
- internal static string? ResolveConfiguredFaceMatcherEncodingsPath(string configuredPath, string fallbackOutputFolderPath)
- {
- var normalizedPath = NormalizeFilePathArgument(configuredPath);
- if (File.Exists(normalizedPath))
- {
- return normalizedPath;
- }
-
- if (Directory.Exists(normalizedPath))
- {
- var fromDirectory = new DirectoryInfo(normalizedPath)
- .EnumerateFiles("*.pkl", SearchOption.TopDirectoryOnly)
- .OrderByDescending(file => file.LastWriteTimeUtc)
- .FirstOrDefault();
- if (fromDirectory is not null)
- {
- return fromDirectory.FullName;
- }
- }
-
- var fallbackOutputFolder = NormalizeDirectoryPathArgument(fallbackOutputFolderPath);
- if (Directory.Exists(fallbackOutputFolder))
- {
- var latest = new DirectoryInfo(fallbackOutputFolder)
- .EnumerateFiles("*.pkl", SearchOption.TopDirectoryOnly)
- .OrderByDescending(file => file.LastWriteTimeUtc)
- .FirstOrDefault();
- return latest?.FullName;
- }
-
- return null;
- }
-
internal static string? ResolveConfiguredFaceEncoderExecutablePath(string configuredPath, bool useGpu)
{
var variant = useGpu ? "gpu" : "cpu";
@@ -2983,37 +2171,6 @@ namespace ImageCatalog_2
};
}
- internal static (string CsvPath, string LogPath) ResolveFaceMatcherOutputPaths(string configuredCsvPath, string configuredLogPath, string fallbackRootPath, string imagePath, DateTime timestamp)
- {
- var baseName = BuildSafeFaceMatcherImageName(imagePath);
- var timestampToken = timestamp.ToString("yyyyMMdd_HHmmss");
- var csvPath = ResolveFaceMatcherOutputFilePath(configuredCsvPath, fallbackRootPath, $"result_{timestampToken}_{baseName}.csv");
- var logPath = ResolveFaceMatcherOutputFilePath(configuredLogPath, fallbackRootPath, $"matcher_log_{timestampToken}_{baseName}.txt");
- return (csvPath, logPath);
- }
-
- internal static string BuildSafeFaceMatcherImageName(string imagePath)
- {
- var fileName = Path.GetFileNameWithoutExtension(imagePath);
- if (string.IsNullOrWhiteSpace(fileName))
- {
- return "image";
- }
-
- var invalidChars = Path.GetInvalidFileNameChars();
- var builder = new StringBuilder(fileName.Length);
- foreach (var currentChar in fileName)
- {
- builder.Append(invalidChars.Contains(currentChar) || char.IsWhiteSpace(currentChar) ? '_' : currentChar);
- }
-
- return builder.ToString().Trim('_') switch
- {
- "" => "image",
- var sanitized => sanitized
- };
- }
-
private static IEnumerable EnumerateFaceEncoderExecutableCandidates(string configuredPath, string variant)
{
var normalizedPath = NormalizeFilePathArgument(configuredPath);
@@ -3057,50 +2214,11 @@ namespace ImageCatalog_2
}
}
- private static IEnumerable EnumerateFaceMatcherExecutableCandidates(string configuredPath, string fallbackEncoderPath)
- {
- var normalizedPath = NormalizeFilePathArgument(configuredPath);
- if (!string.IsNullOrWhiteSpace(normalizedPath))
- {
- if (File.Exists(normalizedPath))
- {
- yield return normalizedPath;
- }
-
- yield return Path.Combine(normalizedPath, "face_matcher.exe");
- }
-
- var fallbackPath = NormalizeFilePathArgument(fallbackEncoderPath);
- if (!string.IsNullOrWhiteSpace(fallbackPath))
- {
- if (File.Exists(fallbackPath))
- {
- var fileDirectory = Path.GetDirectoryName(fallbackPath);
- if (!string.IsNullOrWhiteSpace(fileDirectory))
- {
- yield return Path.Combine(fileDirectory, "face_matcher.exe");
- }
- }
-
- yield return Path.Combine(fallbackPath, "face_matcher.exe");
- }
- }
-
private static int NormalizeFaceParallelism(int value)
{
return value is >= 1 and <= 5 ? value : 3;
}
- private static double NormalizeFaceMatcherTolerance(double value)
- {
- if (double.IsNaN(value) || double.IsInfinity(value))
- {
- return 0.5;
- }
-
- return Math.Clamp(Math.Round(value, 2), 0.35, 0.75);
- }
-
private static int NormalizeNumberAiWorkloadLevel(int value)
{
return value is >= 1 and <= 5 ? value : 3;
@@ -3178,299 +2296,6 @@ namespace ImageCatalog_2
return summary.ToString();
}
- private static string BuildFaceMatcherSummary(
- int exitCode,
- ProcessStartInfo processStartInfo,
- string csvPath,
- string logFilePath,
- StringBuilder outputLines,
- StringBuilder errorLines,
- int matchCount)
- {
- var summary = new StringBuilder();
- summary.AppendLine($"Exit code: {exitCode}");
- summary.AppendLine($"Command: {processStartInfo.FileName} {string.Join(" ", processStartInfo.ArgumentList)}");
- summary.AppendLine($"Result CSV: {csvPath}");
- summary.AppendLine($"Log file: {logFilePath}");
- summary.AppendLine($"Match count: {matchCount}");
-
- lock (outputLines)
- {
- if (outputLines.Length > 0)
- {
- summary.AppendLine();
- summary.AppendLine("STDOUT:");
- summary.Append(outputLines);
- }
- }
-
- lock (errorLines)
- {
- if (errorLines.Length > 0)
- {
- summary.AppendLine();
- summary.AppendLine("STDERR:");
- summary.Append(errorLines);
- }
- }
-
- return summary.ToString();
- }
-
- private static string ResolveFaceMatcherFallbackOutputRoot(string configuredCsvPath, string configuredLogPath, string faceOutputFolderPath, string executablePath)
- {
- foreach (var candidate in new[] { NormalizeFilePathArgument(configuredCsvPath), NormalizeFilePathArgument(configuredLogPath), NormalizeDirectoryPathArgument(faceOutputFolderPath) })
- {
- if (string.IsNullOrWhiteSpace(candidate))
- {
- continue;
- }
-
- if (Directory.Exists(candidate))
- {
- return candidate;
- }
-
- var directory = Path.GetDirectoryName(candidate);
- if (!string.IsNullOrWhiteSpace(directory))
- {
- return directory;
- }
- }
-
- return Path.Combine(Path.GetDirectoryName(executablePath) ?? Environment.CurrentDirectory, "output");
- }
-
- private static string ResolveFaceMatcherOutputFilePath(string configuredPath, string fallbackRootPath, string defaultFileName)
- {
- var normalized = NormalizeFilePathArgument(configuredPath);
- if (string.IsNullOrWhiteSpace(normalized))
- {
- return Path.Combine(fallbackRootPath, defaultFileName);
- }
-
- if (Directory.Exists(normalized) || string.IsNullOrWhiteSpace(Path.GetExtension(normalized)))
- {
- return Path.Combine(normalized, defaultFileName);
- }
-
- return normalized;
- }
-
- private async Task LogIndicatesNoFacesAsync(string logPath)
- {
- try
- {
- if (!File.Exists(logPath))
- {
- return false;
- }
-
- var content = await File.ReadAllTextAsync(logPath).ConfigureAwait(false);
- return content.Contains("nessun volt", StringComparison.OrdinalIgnoreCase)
- || content.Contains("no face", StringComparison.OrdinalIgnoreCase)
- || content.Contains("0 faces", StringComparison.OrdinalIgnoreCase);
- }
- catch
- {
- return false;
- }
- }
-
- private static async Task> ParseFaceMatcherCsvAsync(string csvPath, string logPath)
- {
- var parsedRows = new List();
- if (!File.Exists(csvPath))
- {
- return parsedRows;
- }
-
- var logScores = await ParseFaceMatcherScoresFromLogAsync(logPath).ConfigureAwait(false);
-
- var lines = await File.ReadAllLinesAsync(csvPath).ConfigureAwait(false);
- var meaningfulLines = lines.Where(line => !string.IsNullOrWhiteSpace(line)).ToArray();
- if (meaningfulLines.Length == 0)
- {
- return parsedRows;
- }
-
- string[]? headers = null;
- var firstCells = ParseCsvLine(meaningfulLines[0]);
- var hasHeader = firstCells.Any(cell => cell.Contains("file", StringComparison.OrdinalIgnoreCase)
- || cell.Contains("image", StringComparison.OrdinalIgnoreCase)
- || cell.Contains("score", StringComparison.OrdinalIgnoreCase)
- || cell.Contains("distance", StringComparison.OrdinalIgnoreCase)
- || cell.Contains("confidence", StringComparison.OrdinalIgnoreCase));
-
- var startIndex = 0;
- if (hasHeader)
- {
- headers = firstCells;
- startIndex = 1;
- }
-
- for (var index = startIndex; index < meaningfulLines.Length; index++)
- {
- var rawLine = meaningfulLines[index].Trim();
- var cells = ParseCsvLine(rawLine);
- if (cells.Length == 0 || string.IsNullOrWhiteSpace(cells[0]))
- {
- continue;
- }
-
- var photoId = Path.GetFileName(cells[0]);
- double? score = null;
- for (var cellIndex = 1; cellIndex < cells.Length; cellIndex++)
- {
- if (double.TryParse(cells[cellIndex], NumberStyles.Float, CultureInfo.InvariantCulture, out var parsedScore))
- {
- score = parsedScore;
- break;
- }
- }
-
- if (!score.HasValue && logScores.TryGetValue(photoId, out var logScore))
- {
- score = logScore;
- }
-
- var debugParts = new List();
- for (var cellIndex = 1; cellIndex < cells.Length; cellIndex++)
- {
- var cellValue = cells[cellIndex];
- if (string.IsNullOrWhiteSpace(cellValue))
- {
- continue;
- }
-
- var header = headers is not null && cellIndex < headers.Length
- ? headers[cellIndex]
- : $"col{cellIndex + 1}";
- debugParts.Add($"{header}: {cellValue}");
- }
-
- if (score.HasValue)
- {
- debugParts.Add($"score: {score.Value.ToString("0.###", CultureInfo.InvariantCulture)}");
- }
-
- parsedRows.Add(new ParsedFaceMatcherRow(photoId, score, rawLine, string.Join(" | ", debugParts)));
- }
-
- return parsedRows;
- }
-
- private static async Task> ParseFaceMatcherScoresFromLogAsync(string logPath)
- {
- var scores = new Dictionary(StringComparer.OrdinalIgnoreCase);
- if (!File.Exists(logPath))
- {
- return scores;
- }
-
- var lines = await File.ReadAllLinesAsync(logPath).ConfigureAwait(false);
- foreach (var line in lines)
- {
- if (string.IsNullOrWhiteSpace(line))
- {
- continue;
- }
-
- var match = Regex.Match(
- line,
- @"in\s+(?.+?)\s+-\s+\[Somiglianza:\s*(?[0-9]+(?:[\.,][0-9]+)?)%\]",
- RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
-
- if (!match.Success)
- {
- continue;
- }
-
- var pathValue = match.Groups["path"].Value.Trim();
- var photoId = Path.GetFileName(pathValue);
- if (string.IsNullOrWhiteSpace(photoId))
- {
- continue;
- }
-
- var scoreText = match.Groups["score"].Value.Replace(',', '.');
- if (!double.TryParse(scoreText, NumberStyles.Float, CultureInfo.InvariantCulture, out var score))
- {
- continue;
- }
-
- if (!scores.TryGetValue(photoId, out var existingScore) || score > existingScore)
- {
- scores[photoId] = score;
- }
- }
-
- return scores;
- }
-
- private static string[] ParseCsvLine(string line)
- {
- var values = new List();
- var current = new StringBuilder();
- var insideQuotes = false;
-
- foreach (var currentChar in line)
- {
- if (currentChar == '"')
- {
- insideQuotes = !insideQuotes;
- continue;
- }
-
- if (currentChar == ',' && !insideQuotes)
- {
- values.Add(current.ToString().Trim());
- current.Clear();
- continue;
- }
-
- current.Append(currentChar);
- }
-
- values.Add(current.ToString().Trim());
- return values.ToArray();
- }
-
- private static Dictionary> ResolveDestinationImagesByFileName(string destinationRoot, IEnumerable fileNames)
- {
- var result = new Dictionary>(StringComparer.OrdinalIgnoreCase);
- var normalizedRoot = NormalizeDirectoryPathArgument(destinationRoot);
- if (string.IsNullOrWhiteSpace(normalizedRoot) || !Directory.Exists(normalizedRoot))
- {
- return result;
- }
-
- var requestedNames = new HashSet(fileNames.Where(name => !string.IsNullOrWhiteSpace(name)).Select(name => Path.GetFileName(name)), StringComparer.OrdinalIgnoreCase);
- if (requestedNames.Count == 0)
- {
- return result;
- }
-
- foreach (var path in Directory.EnumerateFiles(normalizedRoot, "*", SearchOption.AllDirectories))
- {
- var fileName = Path.GetFileName(path);
- if (!requestedNames.Contains(fileName))
- {
- continue;
- }
-
- if (!result.TryGetValue(fileName, out var list))
- {
- list = new List();
- result[fileName] = list;
- }
-
- list.Add(path);
- }
-
- return result;
- }
-
private static string NormalizeDirectoryPathArgument(string value)
{
if (string.IsNullOrWhiteSpace(value))
diff --git a/imagecatalog/ImageCatalog 2.csproj b/imagecatalog/ImageCatalog 2.csproj
index 11eda12..734b648 100644
--- a/imagecatalog/ImageCatalog 2.csproj
+++ b/imagecatalog/ImageCatalog 2.csproj
@@ -3,7 +3,6 @@
enable
enable
False
- false
ImageCatalog
default
@@ -47,16 +46,12 @@
embedded
-
-
-
-
SettingsSingleFileGenerator
@@ -70,16 +65,16 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
all
@@ -164,13 +159,4 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/imagecatalog/Models/FaceMatcherResultItem.cs b/imagecatalog/Models/FaceMatcherResultItem.cs
deleted file mode 100644
index 15914e0..0000000
--- a/imagecatalog/Models/FaceMatcherResultItem.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace ImageCatalog_2.Models;
-
-public sealed class FaceMatcherResultItem
-{
- public string PhotoId { get; init; } = string.Empty;
-
- public double? Score { get; init; }
-
- public string ScoreDisplay => Score.HasValue ? Score.Value.ToString("0.###") : string.Empty;
-
- public string ResolvedImagePath { get; init; } = string.Empty;
-
- public int CandidateCount { get; init; }
-
- public string RawRow { get; init; } = string.Empty;
-
- public string DebugSummary { get; init; } = string.Empty;
-
- public string SearchImagePath { get; init; } = string.Empty;
-
- public string CsvPath { get; init; } = string.Empty;
-
- public string LogPath { get; init; } = string.Empty;
-}
diff --git a/imagecatalog/Models/SettingsDto.cs b/imagecatalog/Models/SettingsDto.cs
index fd68763..e9de47f 100644
--- a/imagecatalog/Models/SettingsDto.cs
+++ b/imagecatalog/Models/SettingsDto.cs
@@ -314,18 +314,6 @@ namespace ImageCatalog_2.Models
[XmlElement("AI_FaceUpsample")]
public bool FaceUpsample { get; set; } = true;
- [JsonPropertyName("UseFaceGpu")]
- [XmlElement("AI_UsaGpuFace")]
- public bool UseFaceGpu { get; set; }
-
- [JsonPropertyName("FaceMatcherExecutablePath")]
- [XmlElement("AI_FaceMatcherExecutablePath")]
- public string FaceMatcherExecutablePath { get; set; } = string.Empty;
-
- [JsonPropertyName("FaceMatcherTolerance")]
- [XmlElement("AI_FaceMatcherTolerance")]
- public double FaceMatcherTolerance { get; set; } = 0.5;
-
// Race upload settings
[JsonPropertyName("ApiLogin")]
[XmlElement("RaceUpload_Login")]
diff --git a/imagecatalog/Program.cs b/imagecatalog/Program.cs
index 798a03d..2a350bd 100644
--- a/imagecatalog/Program.cs
+++ b/imagecatalog/Program.cs
@@ -154,7 +154,6 @@ static class Program
var userPrefsPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"ImageCatalog", "userprefs.xml");
services.AddSingleton(new ParametriSetup(userPrefsPath));
- services.AddSingleton();
services.AddSingleton();
services.AddCatalogCommunication(options =>
diff --git a/imagecatalog/Services/AiExtractionService.cs b/imagecatalog/Services/AiExtractionService.cs
index 685a43f..b7ea6cc 100644
--- a/imagecatalog/Services/AiExtractionService.cs
+++ b/imagecatalog/Services/AiExtractionService.cs
@@ -198,7 +198,20 @@ public class AiExtractionService : IAiExtractionService
{
try
{
- WriteCsvOutput(request.CsvOutputPath, extractedResults);
+ var dir = Path.GetDirectoryName(request.CsvOutputPath) ?? string.Empty;
+ if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
+ {
+ Directory.CreateDirectory(dir);
+ }
+
+ using var sw = new StreamWriter(request.CsvOutputPath, false, Encoding.UTF8);
+ sw.WriteLine("Path,Text");
+ foreach (var r in extractedResults)
+ {
+ var csvFileName = Path.GetFileName(r.Path ?? string.Empty);
+ var safeText = (r.Text ?? string.Empty).Replace("\"", "\"\"");
+ sw.WriteLine($"\"{csvFileName}\",\"{safeText}\"");
+ }
}
catch (Exception ex)
{
@@ -209,24 +222,6 @@ public class AiExtractionService : IAiExtractionService
return summary;
}
- internal static void WriteCsvOutput(string csvOutputPath, IEnumerable extractedResults)
- {
- var dir = Path.GetDirectoryName(csvOutputPath) ?? string.Empty;
- if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
- {
- Directory.CreateDirectory(dir);
- }
-
- using var sw = new StreamWriter(csvOutputPath, false, Encoding.UTF8);
- sw.WriteLine("filename,text");
- foreach (var result in extractedResults)
- {
- var csvFileName = Path.GetFileName(result.Path ?? string.Empty);
- var safeText = (result.Text ?? string.Empty).Replace("\"", "\"\"");
- sw.WriteLine($"\"{csvFileName}\",\"{safeText}\"");
- }
- }
-
private static double CalculateAverageImagesPerSecond(int processed, TimeSpan elapsed)
{
return elapsed.TotalSeconds > 0 ? processed / elapsed.TotalSeconds : 0;
diff --git a/imagecatalog/Services/PathShellService.cs b/imagecatalog/Services/PathShellService.cs
deleted file mode 100644
index fcba122..0000000
--- a/imagecatalog/Services/PathShellService.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System.Diagnostics;
-using System.IO;
-
-namespace ImageCatalog_2.Services;
-
-public static class PathShellService
-{
- public 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
- {
- // Ignore failures when opening Explorer.
- }
- }
-}
\ No newline at end of file
diff --git a/imagecatalog/Services/PickerPreferenceService.cs b/imagecatalog/Services/PickerPreferenceService.cs
deleted file mode 100644
index 49bdaea..0000000
--- a/imagecatalog/Services/PickerPreferenceService.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-using Avalonia.Platform.Storage;
-using ImageCatalog;
-using System;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace ImageCatalog_2.Services;
-
-public static class PickerPreferenceKeys
-{
- public const string SourceFolder = "Picker.SourceFolder.LastPath";
- public const string DestinationFolder = "Picker.DestinationFolder.LastPath";
- public const string LogoFile = "Picker.LogoFile.LastPath";
- public const string ModelsFolder = "Picker.ModelsFolder.LastPath";
- public const string CsvOutput = "Picker.CsvOutput.LastPath";
- public const string SettingsFile = "Picker.SettingsFile.LastPath";
- public const string LastSettingsFile = "Settings.LastFilePath";
- public const string FaceExecutableFolder = "Picker.FaceExecutableFolder.LastPath";
- public const string FaceOutputFolder = "Picker.FaceOutputFolder.LastPath";
- public const string FaceMatcherExecutable = "Picker.FaceMatcherExecutable.LastPath";
- public const string FaceMatcherImage = "Picker.FaceMatcherImage.LastPath";
- public const string FaceMatcherEncodings = "Picker.FaceMatcherEncodings.LastPath";
- public const string FaceMatcherOutput = "Picker.FaceMatcherOutput.LastPath";
- public const string FaceMatcherLog = "Picker.FaceMatcherLog.LastPath";
-}
-
-public sealed class PickerPreferenceService
-{
- private readonly ParametriSetup _userPreferences;
-
- public PickerPreferenceService(ParametriSetup userPreferences)
- {
- _userPreferences = userPreferences;
- }
-
- public async Task TryGetStartFolderAsync(IStorageProvider storageProvider, string preferenceKey, string? currentPath = null)
- {
- var startPath = GetPreferredStartDirectory(preferenceKey, currentPath);
- if (string.IsNullOrWhiteSpace(startPath))
- {
- return null;
- }
-
- try
- {
- return await storageProvider.TryGetFolderFromPathAsync(new Uri(startPath)).ConfigureAwait(true);
- }
- catch
- {
- return null;
- }
- }
-
- public void RememberPath(string preferenceKey, string? selectedPath)
- {
- var directory = TryGetExistingDirectory(selectedPath);
- if (string.IsNullOrWhiteSpace(directory))
- {
- return;
- }
-
- _userPreferences.AggiornaParametro(preferenceKey, directory);
- _userPreferences.SalvaParametriSetup();
- }
-
- public string? GetRememberedValue(string preferenceKey)
- {
- var value = _userPreferences.LeggiParametroString(preferenceKey);
- return string.IsNullOrWhiteSpace(value)
- ? null
- : value.Trim().Trim('"');
- }
-
- public void RememberValue(string preferenceKey, string? value)
- {
- if (string.IsNullOrWhiteSpace(value))
- {
- return;
- }
-
- _userPreferences.AggiornaParametro(preferenceKey, value.Trim().Trim('"'));
- _userPreferences.SalvaParametriSetup();
- }
-
- public void ForgetValue(string preferenceKey)
- {
- if (_userPreferences.RimuoviParametro(preferenceKey))
- {
- _userPreferences.SalvaParametriSetup();
- }
- }
-
- private string? GetPreferredStartDirectory(string preferenceKey, string? currentPath)
- {
- var storedPath = _userPreferences.LeggiParametroString(preferenceKey);
- return TryGetExistingDirectory(storedPath) ?? TryGetExistingDirectory(currentPath);
- }
-
- private static string? TryGetExistingDirectory(string? path)
- {
- if (string.IsNullOrWhiteSpace(path))
- {
- return null;
- }
-
- var normalizedPath = path.Trim().Trim('"');
- if (Directory.Exists(normalizedPath))
- {
- return normalizedPath;
- }
-
- if (File.Exists(normalizedPath))
- {
- return Path.GetDirectoryName(normalizedPath);
- }
-
- var containingDirectory = Path.GetDirectoryName(normalizedPath);
- return !string.IsNullOrWhiteSpace(containingDirectory) && Directory.Exists(containingDirectory)
- ? containingDirectory
- : null;
- }
-}
\ No newline at end of file
diff --git a/imagecatalog/ViewModels/AiSettingsViewModel.cs b/imagecatalog/ViewModels/AiSettingsViewModel.cs
index e38213c..501cf5e 100644
--- a/imagecatalog/ViewModels/AiSettingsViewModel.cs
+++ b/imagecatalog/ViewModels/AiSettingsViewModel.cs
@@ -225,105 +225,6 @@ public class AiSettingsViewModel : ViewModelBase
}
}
- private string _faceMatcherExecutablePath = string.Empty;
- public string FaceMatcherExecutablePath
- {
- get => _faceMatcherExecutablePath;
- set
- {
- _faceMatcherExecutablePath = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceMatcherEncodingsPath = string.Empty;
- public string FaceMatcherEncodingsPath
- {
- get => _faceMatcherEncodingsPath;
- set
- {
- _faceMatcherEncodingsPath = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceMatcherOutputPath = string.Empty;
- public string FaceMatcherOutputPath
- {
- get => _faceMatcherOutputPath;
- set
- {
- _faceMatcherOutputPath = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceMatcherLogPath = string.Empty;
- public string FaceMatcherLogPath
- {
- get => _faceMatcherLogPath;
- set
- {
- _faceMatcherLogPath = value;
- NotifyPropertyChanged();
- }
- }
-
- private double _faceMatcherTolerance = 0.5;
- public double FaceMatcherTolerance
- {
- get => _faceMatcherTolerance;
- set
- {
- _faceMatcherTolerance = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceMatcherSelectedImagePath = string.Empty;
- public string FaceMatcherSelectedImagePath
- {
- get => _faceMatcherSelectedImagePath;
- set
- {
- _faceMatcherSelectedImagePath = value;
- NotifyPropertyChanged();
- }
- }
-
- private bool _isFaceMatcherRunning;
- public bool IsFaceMatcherRunning
- {
- get => _isFaceMatcherRunning;
- set
- {
- _isFaceMatcherRunning = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceMatcherStatusMessage = string.Empty;
- public string FaceMatcherStatusMessage
- {
- get => _faceMatcherStatusMessage;
- set
- {
- _faceMatcherStatusMessage = value;
- NotifyPropertyChanged();
- }
- }
-
- private string _faceMatcherCommandOutput = string.Empty;
- public string FaceMatcherCommandOutput
- {
- get => _faceMatcherCommandOutput;
- set
- {
- _faceMatcherCommandOutput = value;
- NotifyPropertyChanged();
- }
- }
-
private double _aiProgress;
public double AiProgress
{
@@ -336,6 +237,4 @@ public class AiSettingsViewModel : ViewModelBase
}
public ObservableCollection PreviewResults { get; } = new();
-
- public ObservableCollection FaceMatcherResults { get; } = new();
}