feat: Enhance Face AI upload functionality and UI
- Updated MainWindow.axaml to increase height and add new UI elements for SSH upload configuration. - Implemented commands for opening source and destination paths in file explorer. - Added FaceUploadPath and SSH configuration properties to DataModel and AiSettingsViewModel. - Introduced validation for FaceUploadPath format and commands for uploading face encoder output. - Enhanced PickerPreferenceService to manage SSH credentials and upload preferences. - Updated settings persistence to include FaceUploadPath and SSH preferences. - Added tests for FaceUploadPath validation and upload command enabling logic.
This commit is contained in:
parent
e68608312a
commit
e9142df97c
22 changed files with 1477 additions and 84 deletions
|
|
@ -283,9 +283,120 @@ public class DataModelCharacterizationTests
|
|||
output.LogFilePath.ShouldBe(@"C:\out\encoder_log_20260509_143045_04_APRILE_gara.txt");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FaceUploadPath_ValidatesExpectedRelativePathShape()
|
||||
{
|
||||
DataModel.IsValidFaceUploadPath("2026/05.MAGGIO/EMPOLI").ShouldBeTrue();
|
||||
DataModel.IsValidFaceUploadPath("2026/5.MAGGIO/EMPOLI").ShouldBeFalse();
|
||||
DataModel.IsValidFaceUploadPath("2026/00.MAGGIO/EMPOLI").ShouldBeFalse();
|
||||
DataModel.IsValidFaceUploadPath("2026/13.MAGGIO/EMPOLI").ShouldBeFalse();
|
||||
DataModel.IsValidFaceUploadPath("2026/05.MAGGIO").ShouldBeFalse();
|
||||
|
||||
DataModel.CombineRemoteUploadPath("/mnt/da1/foto/", "2026/05.MAGGIO/EMPOLI")
|
||||
.ShouldBe("/mnt/da1/foto/2026/05.MAGGIO/EMPOLI");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FaceUploadCommand_IsEnabledOnlyForValidUploadPath()
|
||||
{
|
||||
var model = CreateModel();
|
||||
|
||||
model.UploadFaceEncoderOutputCommand.CanExecute(null).ShouldBeFalse();
|
||||
|
||||
model.FaceUploadPath = "2026/05.MAGGIO/EMPOLI";
|
||||
model.UploadFaceEncoderOutputCommand.CanExecute(null).ShouldBeTrue();
|
||||
|
||||
model.FaceUploadPath = "2026/5.MAGGIO/EMPOLI";
|
||||
model.UploadFaceEncoderOutputCommand.CanExecute(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FaceSshPreferences_AreStoredInUserPreferences()
|
||||
{
|
||||
using var tempDirectory = new TemporaryDirectory();
|
||||
var preferencesFile = Path.Combine(tempDirectory.Path, "userprefs.xml");
|
||||
var preferenceService = new PickerPreferenceService(new ImageCatalog.ParametriSetup(preferencesFile));
|
||||
|
||||
var model = CreateModel(pickerPreferenceService: preferenceService);
|
||||
model.FaceSshUsername = "ssh-user";
|
||||
model.FaceSshPassword = "ssh-password";
|
||||
model.FaceSshAddress = "upload.example.org";
|
||||
model.FaceSshPort = "2222";
|
||||
model.FaceSshPathA = "/mnt/da1/foto/";
|
||||
model.FaceSshPathB = "/mnt/nas12/foto/";
|
||||
model.FaceUploadDryRun = true;
|
||||
|
||||
var reloadedPreferenceService = new PickerPreferenceService(new ImageCatalog.ParametriSetup(preferencesFile));
|
||||
var reloaded = CreateModel(pickerPreferenceService: reloadedPreferenceService);
|
||||
|
||||
reloaded.FaceSshUsername.ShouldBe("ssh-user");
|
||||
reloaded.FaceSshPassword.ShouldBe("ssh-password");
|
||||
reloaded.FaceSshAddress.ShouldBe("upload.example.org");
|
||||
reloaded.FaceSshPort.ShouldBe("2222");
|
||||
reloaded.FaceSshPathA.ShouldBe("/mnt/da1/foto/");
|
||||
reloaded.FaceSshPathB.ShouldBe("/mnt/nas12/foto/");
|
||||
reloaded.FaceUploadDryRun.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveLatestFaceUploadSourceFile_UsesLatestPklForCurrentRace()
|
||||
{
|
||||
using var tempDirectory = new TemporaryDirectory();
|
||||
var outputFolder = Path.Combine(tempDirectory.Path, "out");
|
||||
var currentRaceFolder = Path.Combine(tempDirectory.Path, "04 APRILE gara");
|
||||
Directory.CreateDirectory(outputFolder);
|
||||
Directory.CreateDirectory(currentRaceFolder);
|
||||
|
||||
var olderCurrentRace = Path.Combine(outputFolder, "face_encodings_20260509_143045_04_APRILE_gara.pkl");
|
||||
var newerCurrentRace = Path.Combine(outputFolder, "face_encodings_20260509_153045_04_APRILE_gara.pkl");
|
||||
var otherRace = Path.Combine(outputFolder, "face_encodings_20260509_163045_05_MAGGIO_gara.pkl");
|
||||
|
||||
File.WriteAllText(olderCurrentRace, "old");
|
||||
File.WriteAllText(newerCurrentRace, "new");
|
||||
File.WriteAllText(otherRace, "other");
|
||||
|
||||
File.SetLastWriteTimeUtc(olderCurrentRace, new DateTime(2026, 5, 9, 14, 30, 45, DateTimeKind.Utc));
|
||||
File.SetLastWriteTimeUtc(newerCurrentRace, new DateTime(2026, 5, 9, 15, 30, 45, DateTimeKind.Utc));
|
||||
File.SetLastWriteTimeUtc(otherRace, new DateTime(2026, 5, 9, 16, 30, 45, DateTimeKind.Utc));
|
||||
|
||||
var selected = DataModel.ResolveLatestFaceUploadSourceFile(outputFolder, currentRaceFolder);
|
||||
|
||||
selected.ShouldBe(newerCurrentRace);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task SettingsService_PersistsFaceUploadPathButNotSshPreferences()
|
||||
{
|
||||
using var tempDirectory = new TemporaryDirectory();
|
||||
var settingsFile = Path.Combine(tempDirectory.Path, "settings.xml");
|
||||
var userPreferencesFile = Path.Combine(tempDirectory.Path, "userprefs.xml");
|
||||
var preferenceService = new PickerPreferenceService(new ImageCatalog.ParametriSetup(userPreferencesFile));
|
||||
var settingsService = new SettingsService(
|
||||
new ImageCatalog.ParametriSetup(Path.Combine(tempDirectory.Path, "unused.xml")),
|
||||
Substitute.For<ILogger<SettingsService>>());
|
||||
var model = CreateModel(settingsService: settingsService, pickerPreferenceService: preferenceService);
|
||||
|
||||
model.FaceUploadPath = "2026/05.MAGGIO/EMPOLI";
|
||||
model.FaceSshUsername = "ssh-user";
|
||||
|
||||
await settingsService.SaveSettingsAsync(settingsFile, model);
|
||||
|
||||
var xml = File.ReadAllText(settingsFile);
|
||||
xml.ShouldContain("AI_FaceUploadPath");
|
||||
xml.ShouldContain("2026/05.MAGGIO/EMPOLI");
|
||||
xml.ShouldNotContain("AI_FaceUploadDryRun");
|
||||
xml.ShouldNotContain("FaceAI.Ssh");
|
||||
xml.ShouldNotContain("ssh-user");
|
||||
|
||||
var loaded = CreateModel(settingsService: settingsService);
|
||||
await settingsService.LoadSettingsAsync(settingsFile, loaded);
|
||||
loaded.FaceUploadPath.ShouldBe("2026/05.MAGGIO/EMPOLI");
|
||||
}
|
||||
|
||||
private static DataModel CreateModel(
|
||||
ISettingsService? settingsService = null,
|
||||
ITestService? testService = null)
|
||||
ITestService? testService = null,
|
||||
PickerPreferenceService? pickerPreferenceService = null)
|
||||
{
|
||||
var mapper = Substitute.For<AutoMapper.IMapper>();
|
||||
var picSettings = new PicSettings();
|
||||
|
|
@ -316,7 +427,8 @@ public class DataModelCharacterizationTests
|
|||
picSettings,
|
||||
mapper,
|
||||
Substitute.For<ILogger<DataModel>>(),
|
||||
versionProvider: null);
|
||||
versionProvider: null,
|
||||
pickerPreferenceService: pickerPreferenceService);
|
||||
}
|
||||
|
||||
private static string CreateFaceEncoderExecutable(string rootPath, string variant)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue