feat: Update package references and enhance AI extraction service with CSV output functionality
This commit is contained in:
parent
55e8f0face
commit
af74c90ce7
12 changed files with 400 additions and 153 deletions
|
|
@ -8,10 +8,10 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,15 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="4.1.0" />
|
<PackageReference Include="MSTest.TestAdapter" Version="4.2.3" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="4.1.0" />
|
<PackageReference Include="MSTest.TestFramework" Version="4.2.3" />
|
||||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="4.0.0" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
|
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="3.0.0" />
|
||||||
<PackageReference Include="SixLabors.Fonts" Version="2.1.3" />
|
<PackageReference Include="SixLabors.Fonts" Version="3.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
58
MaddoShared.Tests/PickerPreferenceServiceTests.cs
Normal file
58
MaddoShared.Tests/PickerPreferenceServiceTests.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,48 @@
|
||||||
namespace MaddoShared.Tests
|
using ImageCatalog_2.Services;
|
||||||
|
using ImageCatalog_2.Models;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace MaddoShared.Tests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public sealed class AiExtractionServiceCsvTests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestMethod]
|
||||||
public sealed class Test1
|
public void WriteCsvOutput_UsesLegacyCompatibleHeaderAndFilenameColumn()
|
||||||
{
|
{
|
||||||
[TestMethod]
|
using var tempDir = new TempDirectory();
|
||||||
public void TestMethod1()
|
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()
|
||||||
{
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ public partial class AvaloniaMainWindow : Window
|
||||||
private readonly DataModel _model;
|
private readonly DataModel _model;
|
||||||
private readonly PickerPreferenceService _pickerPreferenceService;
|
private readonly PickerPreferenceService _pickerPreferenceService;
|
||||||
private bool _isDarkTheme;
|
private bool _isDarkTheme;
|
||||||
|
private bool _startupSettingsRestoreAttempted;
|
||||||
|
|
||||||
public AvaloniaMainWindow(DataModel model)
|
public AvaloniaMainWindow(DataModel model)
|
||||||
{
|
{
|
||||||
|
|
@ -26,7 +27,11 @@ public partial class AvaloniaMainWindow : Window
|
||||||
_pickerPreferenceService = Program.ServiceProvider.GetRequiredService<PickerPreferenceService>();
|
_pickerPreferenceService = Program.ServiceProvider.GetRequiredService<PickerPreferenceService>();
|
||||||
DataContext = _model;
|
DataContext = _model;
|
||||||
|
|
||||||
Opened += (_, _) => SyncThemeStateFromCurrentTheme();
|
Opened += async (_, _) =>
|
||||||
|
{
|
||||||
|
SyncThemeStateFromCurrentTheme();
|
||||||
|
await TryLoadLastSettingsOnStartupAsync();
|
||||||
|
};
|
||||||
Closing += AvaloniaMainWindow_Closing;
|
Closing += AvaloniaMainWindow_Closing;
|
||||||
|
|
||||||
// Let DataModel marshal callbacks onto Avalonia UI thread.
|
// Let DataModel marshal callbacks onto Avalonia UI thread.
|
||||||
|
|
@ -120,30 +125,38 @@ public partial class AvaloniaMainWindow : Window
|
||||||
|
|
||||||
_model.SaveSettingsRequested += async (_, _) =>
|
_model.SaveSettingsRequested += async (_, _) =>
|
||||||
{
|
{
|
||||||
|
var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(StorageProvider, PickerPreferenceKeys.SettingsFile);
|
||||||
var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||||
{
|
{
|
||||||
Title = "Salva impostazioni",
|
Title = "Salva impostazioni",
|
||||||
DefaultExtension = "xml",
|
DefaultExtension = "xml",
|
||||||
FileTypeChoices = [new FilePickerFileType("Setup") { Patterns = ["*.xml"] }]
|
FileTypeChoices = [new FilePickerFileType("Setup") { Patterns = ["*.xml"] }],
|
||||||
|
SuggestedStartLocation = suggestedStartLocation
|
||||||
});
|
});
|
||||||
|
|
||||||
if (file is not null)
|
if (file is not null)
|
||||||
{
|
{
|
||||||
await _model.SaveSettingsToFileAsync(file.Path.LocalPath);
|
await _model.SaveSettingsToFileAsync(file.Path.LocalPath);
|
||||||
|
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.SettingsFile, file.Path.LocalPath);
|
||||||
|
_pickerPreferenceService.RememberValue(PickerPreferenceKeys.LastSettingsFile, file.Path.LocalPath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_model.LoadSettingsRequested += async (_, _) =>
|
_model.LoadSettingsRequested += async (_, _) =>
|
||||||
{
|
{
|
||||||
|
var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(StorageProvider, PickerPreferenceKeys.SettingsFile);
|
||||||
var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||||
{
|
{
|
||||||
Title = "Carica impostazioni",
|
Title = "Carica impostazioni",
|
||||||
FileTypeFilter = [new FilePickerFileType("Setup") { Patterns = ["*.xml"] }]
|
FileTypeFilter = [new FilePickerFileType("Setup") { Patterns = ["*.xml"] }],
|
||||||
|
SuggestedStartLocation = suggestedStartLocation
|
||||||
});
|
});
|
||||||
|
|
||||||
if (files.Count > 0)
|
if (files.Count > 0)
|
||||||
{
|
{
|
||||||
await _model.LoadSettingsFromFileAsync(files[0].Path.LocalPath);
|
await _model.LoadSettingsFromFileAsync(files[0].Path.LocalPath);
|
||||||
|
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.SettingsFile, files[0].Path.LocalPath);
|
||||||
|
_pickerPreferenceService.RememberValue(PickerPreferenceKeys.LastSettingsFile, files[0].Path.LocalPath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -165,6 +178,38 @@ public partial class AvaloniaMainWindow : Window
|
||||||
|
|
||||||
private bool _isStoppingFaceEncoderForClose;
|
private bool _isStoppingFaceEncoderForClose;
|
||||||
|
|
||||||
|
private async Task TryLoadLastSettingsOnStartupAsync()
|
||||||
|
{
|
||||||
|
if (_startupSettingsRestoreAttempted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_startupSettingsRestoreAttempted = true;
|
||||||
|
|
||||||
|
var lastSettingsFile = _pickerPreferenceService.GetRememberedValue(PickerPreferenceKeys.LastSettingsFile);
|
||||||
|
if (string.IsNullOrWhiteSpace(lastSettingsFile))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(lastSettingsFile))
|
||||||
|
{
|
||||||
|
_pickerPreferenceService.ForgetValue(PickerPreferenceKeys.LastSettingsFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _model.LoadSettingsFromFileAsync(lastSettingsFile);
|
||||||
|
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.SettingsFile, lastSettingsFile);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await ShowMessageDialogAsync("Impostazioni", $"Impossibile caricare il file impostazioni automatico:\n{ex.GetBaseException().Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void AvaloniaMainWindow_Closing(object? sender, CancelEventArgs e)
|
private async void AvaloniaMainWindow_Closing(object? sender, CancelEventArgs e)
|
||||||
{
|
{
|
||||||
if (_isStoppingFaceEncoderForClose || (!_model.IsFaceEncoderRunning && !_model.IsFaceMatcherRunning))
|
if (_isStoppingFaceEncoderForClose || (!_model.IsFaceEncoderRunning && !_model.IsFaceMatcherRunning))
|
||||||
|
|
|
||||||
|
|
@ -3,112 +3,128 @@
|
||||||
xmlns:avaloniaDataGrid="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls.DataGrid"
|
xmlns:avaloniaDataGrid="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls.DataGrid"
|
||||||
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
|
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
|
||||||
x:Class="ImageCatalog_2.AvaloniaViews.AiTabView">
|
x:Class="ImageCatalog_2.AvaloniaViews.AiTabView">
|
||||||
<Grid>
|
<TabControl Margin="4">
|
||||||
<Grid.RowDefinitions>
|
<TabItem Header="Esecuzione">
|
||||||
<RowDefinition Height="Auto" />
|
<Grid RowDefinitions="Auto,*">
|
||||||
<RowDefinition Height="*" />
|
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
|
||||||
</Grid.RowDefinitions>
|
<StackPanel Margin="4" Spacing="8">
|
||||||
|
<TextBlock Text="AI / OCR" FontWeight="Bold" />
|
||||||
|
|
||||||
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
|
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||||
<StackPanel Margin="4">
|
<CheckBox Content="Usa GPU"
|
||||||
<TextBlock Text="AI / OCR" FontWeight="Bold" />
|
IsChecked="{Binding UseNumberAiGpu, Mode=TwoWay}"
|
||||||
<StackPanel Orientation="Horizontal" Spacing="12" Margin="0,6,0,0">
|
IsEnabled="{Binding NumberAiGpuOptionEnabled}" />
|
||||||
<CheckBox Content="Usa GPU"
|
<CheckBox Content="Includi thumbnail" IsChecked="{Binding IncludeNumberAiThumbnails, Mode=TwoWay}" />
|
||||||
IsChecked="{Binding UseNumberAiGpu, Mode=TwoWay}"
|
</StackPanel>
|
||||||
IsEnabled="{Binding NumberAiGpuOptionEnabled}" />
|
|
||||||
<CheckBox Content="Includi thumbnail" IsChecked="{Binding IncludeNumberAiThumbnails, Mode=TwoWay}" />
|
<Grid ColumnDefinitions="Auto,Auto,*" ColumnSpacing="8">
|
||||||
|
<TextBlock Grid.Column="0" Text="Carico OCR:" VerticalAlignment="Center" />
|
||||||
|
<ComboBox Grid.Column="1"
|
||||||
|
Width="84"
|
||||||
|
ItemsSource="{Binding NumberAiWorkloadOptions}"
|
||||||
|
SelectedItem="{Binding NumberAiWorkloadLevel, Mode=TwoWay}" />
|
||||||
|
<TextBlock Grid.Column="2"
|
||||||
|
Text="{Binding NumberAiStatsSummary}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontWeight="SemiBold" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||||
|
<TextBlock Grid.Column="0" Text="Sorgente:" VerticalAlignment="Center" />
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
Text="{Binding DestinationPath, Mode=OneWay}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<Button Grid.Column="2" Width="104" Command="{Binding SelectDestinationFolderCommand}">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||||
|
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
||||||
|
<TextBlock Text="Scegli..." />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Grid.Column="3" Width="72" Click="OpenAiSourceFolder_Click">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||||
|
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||||
|
<TextBlock Text="Apri" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Spacing="8">
|
||||||
|
<Button Command="{Binding StartAiCommand}" Width="132">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||||
|
<iconPacks:PackIconMaterial Kind="PlayCircle" Width="16" Height="16" Foreground="#2E7D32" />
|
||||||
|
<TextBlock Text="Avvia AI" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Command="{Binding AsyncCancelOperationCommand}" Width="132">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||||
|
<iconPacks:PackIconMaterial Kind="Cancel" Width="16" Height="16" Foreground="#C62828" />
|
||||||
|
<TextBlock Text="Annulla" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock Text="Output CSV" FontWeight="Bold" />
|
||||||
|
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||||
|
<TextBlock Text="Percorso CSV:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||||
|
<TextBox Text="{Binding CsvOutputPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||||
|
<Button Width="104" Command="{Binding SelectCsvOutputCommand}" Grid.Column="2">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||||
|
<iconPacks:PackIconMaterial Kind="FileOutline" Width="14" Height="14" />
|
||||||
|
<TextBlock Text="Scegli..." />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Width="72" Grid.Column="3" Click="OpenCsvOutputFolder_Click">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||||
|
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||||
|
<TextBlock Text="Apri" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<TextBlock Text="Anteprima risultati" FontWeight="Bold" />
|
||||||
|
<ProgressBar Minimum="0" Maximum="100" Value="{Binding AiProgress}" Height="16" Margin="0,0,0,4" />
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<avaloniaDataGrid:DataGrid Grid.Row="1"
|
||||||
|
ItemsSource="{Binding PreviewResults}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
Margin="4,4,4,4"
|
||||||
|
CanUserResizeColumns="True"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<avaloniaDataGrid:DataGrid.Columns>
|
||||||
|
<avaloniaDataGrid:DataGridTextColumn Header="Path" Binding="{Binding Path}" Width="*" />
|
||||||
|
<avaloniaDataGrid:DataGridTextColumn Header="Text" Binding="{Binding Text}" Width="2*" />
|
||||||
|
</avaloniaDataGrid:DataGrid.Columns>
|
||||||
|
</avaloniaDataGrid:DataGrid>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem Header="Impostazioni">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel Margin="8" Spacing="8">
|
||||||
|
<TextBlock Text="Modelli" FontWeight="Bold" />
|
||||||
|
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||||
|
<TextBlock Text="Cartella modelli:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||||
|
<TextBox Text="{Binding ModelsFolderPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||||
|
<Button Width="104" Command="{Binding SelectModelsFolderCommand}" Grid.Column="2">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||||
|
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
||||||
|
<TextBlock Text="Scegli..." />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button Width="72" Grid.Column="3" Click="OpenModelsFolder_Click">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||||
|
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||||
|
<TextBlock Text="Apri" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
<Grid Margin="0,8,0,0" ColumnDefinitions="Auto,Auto,*" ColumnSpacing="8">
|
</TabItem>
|
||||||
<TextBlock Grid.Column="0" Text="Carico OCR:" VerticalAlignment="Center" />
|
</TabControl>
|
||||||
<ComboBox Grid.Column="1"
|
|
||||||
Width="84"
|
|
||||||
ItemsSource="{Binding NumberAiWorkloadOptions}"
|
|
||||||
SelectedItem="{Binding NumberAiWorkloadLevel, Mode=TwoWay}" />
|
|
||||||
<TextBlock Grid.Column="2"
|
|
||||||
Text="{Binding NumberAiStatsSummary}"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
FontWeight="SemiBold" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid Margin="0,8,0,0" ColumnDefinitions="Auto,*,Auto" ColumnSpacing="6">
|
|
||||||
<TextBlock Grid.Column="0" Text="Sorgente:" VerticalAlignment="Center" />
|
|
||||||
<TextBox Grid.Column="1" Text="{Binding DestinationPath, Mode=OneWay}" IsReadOnly="True" VerticalAlignment="Center" />
|
|
||||||
<Button Grid.Column="2" Width="72" Click="OpenAiDestinationFolder_Click">
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
|
||||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
|
||||||
<TextBlock Text="Apri" />
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<TextBlock Text="Modelli" FontWeight="Bold" Margin="0,8,0,0" />
|
|
||||||
<Grid Margin="0,4,0,0" ColumnDefinitions="Auto,*,Auto,Auto">
|
|
||||||
<TextBlock Text="Cartella modelli:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
|
||||||
<TextBox Text="{Binding ModelsFolderPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
|
||||||
<Button Width="104" Margin="6,0,0,0" Command="{Binding SelectModelsFolderCommand}"
|
|
||||||
Grid.Column="2">
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
|
||||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
|
||||||
<TextBlock Text="Scegli..." />
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
<Button Width="72" Margin="6,0,0,0" Grid.Column="3"
|
|
||||||
Click="OpenModelsFolder_Click">
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
|
||||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
|
||||||
<TextBlock Text="Apri" />
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,8,0,0" Spacing="8">
|
|
||||||
<Button Command="{Binding StartAiCommand}" Width="132">
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
|
||||||
<iconPacks:PackIconMaterial Kind="PlayCircle" Width="16" Height="16" Foreground="#2E7D32" />
|
|
||||||
<TextBlock Text="Avvia AI" />
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
<Button Command="{Binding AsyncCancelOperationCommand}" Width="132">
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
|
||||||
<iconPacks:PackIconMaterial Kind="Cancel" Width="16" Height="16" Foreground="#C62828" />
|
|
||||||
<TextBlock Text="Annulla" />
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<TextBlock Text="Output CSV" FontWeight="Bold" Margin="0,8,0,0" />
|
|
||||||
<Grid Margin="0,4,0,0" ColumnDefinitions="Auto,*,Auto,Auto">
|
|
||||||
<TextBlock Text="Percorso CSV:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
|
||||||
<TextBox Text="{Binding CsvOutputPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
|
||||||
<Button Width="104" Margin="6,0,0,0" Command="{Binding SelectCsvOutputCommand}"
|
|
||||||
Grid.Column="2">
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
|
||||||
<iconPacks:PackIconMaterial Kind="FileOutline" Width="14" Height="14" />
|
|
||||||
<TextBlock Text="Scegli..." />
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
<Button Width="72" Margin="6,0,0,0" Grid.Column="3"
|
|
||||||
Click="OpenCsvOutputFolder_Click">
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
|
||||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
|
||||||
<TextBlock Text="Apri" />
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<TextBlock Text="Anteprima risultati" FontWeight="Bold" Margin="0,8,0,0" />
|
|
||||||
<ProgressBar Minimum="0" Maximum="100" Value="{Binding AiProgress}" Height="16" Margin="0,4,0,4" />
|
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
|
||||||
|
|
||||||
<avaloniaDataGrid:DataGrid Grid.Row="1" ItemsSource="{Binding PreviewResults}" IsReadOnly="True"
|
|
||||||
AutoGenerateColumns="False" Margin="4,4,4,4" CanUserResizeColumns="True" VerticalAlignment="Stretch">
|
|
||||||
<avaloniaDataGrid:DataGrid.Columns>
|
|
||||||
<avaloniaDataGrid:DataGridTextColumn Header="Path" Binding="{Binding Path}" Width="*" />
|
|
||||||
<avaloniaDataGrid:DataGridTextColumn Header="Text" Binding="{Binding Text}" Width="2*" />
|
|
||||||
</avaloniaDataGrid:DataGrid.Columns>
|
|
||||||
</avaloniaDataGrid:DataGrid>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public partial class AiTabView : Avalonia.Controls.UserControl
|
||||||
OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? model.CsvOutputPath : directory);
|
OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? model.CsvOutputPath : directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenAiDestinationFolder_Click(object? sender, RoutedEventArgs e)
|
private void OpenAiSourceFolder_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is DataModel model)
|
if (DataContext is DataModel model)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,8 @@ namespace ImageCatalog_2
|
||||||
private Task? _faceMatcherLogWatcherTask;
|
private Task? _faceMatcherLogWatcherTask;
|
||||||
private bool _hasStartedFaceEncoderInSession;
|
private bool _hasStartedFaceEncoderInSession;
|
||||||
private bool _hasStartedFaceMatcherInSession;
|
private bool _hasStartedFaceMatcherInSession;
|
||||||
|
private int _numberAiGpuRefreshVersion;
|
||||||
|
private volatile bool _numberAiGpuValidationPending;
|
||||||
|
|
||||||
private sealed record ParsedFaceMatcherRow(string PhotoId, double? Score, string RawRow, string DebugSummary);
|
private sealed record ParsedFaceMatcherRow(string PhotoId, double? Score, string RawRow, string DebugSummary);
|
||||||
|
|
||||||
|
|
@ -137,7 +139,7 @@ namespace ImageCatalog_2
|
||||||
|
|
||||||
// Load available fonts
|
// Load available fonts
|
||||||
AvailableFonts = LoadAvailableFonts();
|
AvailableFonts = LoadAvailableFonts();
|
||||||
RefreshNumberAiGpuCapabilities();
|
QueueRefreshNumberAiGpuCapabilities();
|
||||||
RefreshFaceExecutableCapabilities();
|
RefreshFaceExecutableCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +159,7 @@ namespace ImageCatalog_2
|
||||||
_logger.LogError(ex, "AI extraction failed");
|
_logger.LogError(ex, "AI extraction failed");
|
||||||
if (UseNumberAiGpu)
|
if (UseNumberAiGpu)
|
||||||
{
|
{
|
||||||
RefreshNumberAiGpuCapabilities();
|
QueueRefreshNumberAiGpuCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
await InvokeOnUiThreadAsync(() => NumberAiStatsSummary = $"Errore OCR: {ex.GetBaseException().Message}").ConfigureAwait(false);
|
await InvokeOnUiThreadAsync(() => NumberAiStatsSummary = $"Errore OCR: {ex.GetBaseException().Message}").ConfigureAwait(false);
|
||||||
|
|
@ -255,7 +257,7 @@ namespace ImageCatalog_2
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_ai.ModelsFolderPath = value;
|
_ai.ModelsFolderPath = value;
|
||||||
RefreshNumberAiGpuCapabilities();
|
QueueRefreshNumberAiGpuCapabilities();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2281,31 +2283,86 @@ namespace ImageCatalog_2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshNumberAiGpuCapabilities()
|
private void QueueRefreshNumberAiGpuCapabilities()
|
||||||
{
|
{
|
||||||
if (!TryBuildNumberAiModelConfiguration(out var configuration))
|
if (!TryBuildNumberAiModelConfiguration(out var configuration))
|
||||||
{
|
{
|
||||||
|
_numberAiGpuValidationPending = false;
|
||||||
NumberAiGpuOptionEnabled = false;
|
NumberAiGpuOptionEnabled = false;
|
||||||
_ai.UseNumberAiGpu = false;
|
_ai.UseNumberAiGpu = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberAiGpuOptionEnabled = NumberRecognitionEngine.TryValidateGpuRuntime(configuration, _logger, out _);
|
_numberAiGpuValidationPending = true;
|
||||||
if (!NumberAiGpuOptionEnabled)
|
var requestVersion = Interlocked.Increment(ref _numberAiGpuRefreshVersion);
|
||||||
|
_ = RefreshNumberAiGpuCapabilitiesAsync(configuration, requestVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshNumberAiGpuCapabilitiesAsync(ModelConfiguration configuration, int requestVersion)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_ai.UseNumberAiGpu = false;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetUseNumberAiGpu(bool value)
|
private void SetUseNumberAiGpu(bool value)
|
||||||
{
|
{
|
||||||
if (!NumberAiGpuOptionEnabled)
|
if (!value)
|
||||||
{
|
{
|
||||||
_ai.UseNumberAiGpu = false;
|
_ai.UseNumberAiGpu = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ai.UseNumberAiGpu = value;
|
if (NumberAiGpuOptionEnabled || _numberAiGpuValidationPending)
|
||||||
|
{
|
||||||
|
_ai.UseNumberAiGpu = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ai.UseNumberAiGpu = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryBuildNumberAiModelConfiguration(out ModelConfiguration configuration)
|
private bool TryBuildNumberAiModelConfiguration(out ModelConfiguration configuration)
|
||||||
|
|
|
||||||
|
|
@ -65,16 +65,16 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AIFotoONLUS.Core" Version="0.1.2" Condition="'$(UseLocalAIFotoONLUS)' != 'true'" />
|
<PackageReference Include="AIFotoONLUS.Core" Version="0.1.2" Condition="'$(UseLocalAIFotoONLUS)' != 'true'" />
|
||||||
<PackageReference Include="AutoMapper" Version="16.1.0" />
|
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||||
<PackageReference Include="IconPacks.Avalonia" Version="1.3.1" />
|
<PackageReference Include="IconPacks.Avalonia" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.8" />
|
||||||
<PackageReference Include="MinVer" Version="7.0.0" PrivateAssets="all" />
|
<PackageReference Include="MinVer" Version="7.0.0" PrivateAssets="all" />
|
||||||
<PackageReference Include="Avalonia" Version="11.3.12" />
|
<PackageReference Include="Avalonia" Version="12.0.3" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
<PackageReference Include="Avalonia.Desktop" Version="12.0.3" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.12" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.3" />
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="12.0.0" />
|
||||||
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.421302">
|
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.421302">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
||||||
|
|
@ -198,20 +198,7 @@ public class AiExtractionService : IAiExtractionService
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var dir = Path.GetDirectoryName(request.CsvOutputPath) ?? string.Empty;
|
WriteCsvOutput(request.CsvOutputPath, extractedResults);
|
||||||
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -222,6 +209,24 @@ public class AiExtractionService : IAiExtractionService
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void WriteCsvOutput(string csvOutputPath, IEnumerable<AiResultItem> 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)
|
private static double CalculateAverageImagesPerSecond(int processed, TimeSpan elapsed)
|
||||||
{
|
{
|
||||||
return elapsed.TotalSeconds > 0 ? processed / elapsed.TotalSeconds : 0;
|
return elapsed.TotalSeconds > 0 ? processed / elapsed.TotalSeconds : 0;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ public static class PickerPreferenceKeys
|
||||||
public const string LogoFile = "Picker.LogoFile.LastPath";
|
public const string LogoFile = "Picker.LogoFile.LastPath";
|
||||||
public const string ModelsFolder = "Picker.ModelsFolder.LastPath";
|
public const string ModelsFolder = "Picker.ModelsFolder.LastPath";
|
||||||
public const string CsvOutput = "Picker.CsvOutput.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 FaceExecutableFolder = "Picker.FaceExecutableFolder.LastPath";
|
||||||
public const string FaceOutputFolder = "Picker.FaceOutputFolder.LastPath";
|
public const string FaceOutputFolder = "Picker.FaceOutputFolder.LastPath";
|
||||||
public const string FaceMatcherExecutable = "Picker.FaceMatcherExecutable.LastPath";
|
public const string FaceMatcherExecutable = "Picker.FaceMatcherExecutable.LastPath";
|
||||||
|
|
@ -61,6 +63,33 @@ public sealed class PickerPreferenceService
|
||||||
_userPreferences.SalvaParametriSetup();
|
_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)
|
private string? GetPreferredStartDirectory(string preferenceKey, string? currentPath)
|
||||||
{
|
{
|
||||||
var storedPath = _userPreferences.LeggiParametroString(preferenceKey);
|
var storedPath = _userPreferences.LeggiParametroString(preferenceKey);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue