From 0c33c1ddc161bebfa5385330d4d6a6fccafcd99c Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Tue, 13 Jan 2026 11:12:26 +0100 Subject: [PATCH] Persist upload queue and uploaded cache --- .../PhotoboothUploader/MainWindow.axaml.cs | 108 ++++++++++++++++++ .../Models/PhotoboothSettings.cs | 5 + 2 files changed, 113 insertions(+) diff --git a/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml.cs b/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml.cs index dd41d3c..4a6efae 100644 --- a/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml.cs +++ b/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml.cs @@ -40,6 +40,7 @@ public partial class MainWindow : Window { InitializeComponent(); _settings = _settingsStore.Load(); + EnsureSettingsCollections(); _settings.BaseUrl = NormalizeBaseUrl(_settings.BaseUrl) ?? DefaultBaseUrl; if (_settings.MaxConcurrentUploads <= 0) { @@ -226,6 +227,7 @@ public partial class MainWindow : Window ResetCounters(); _uploadService.Start(_settings, OnQueued, OnUploading, OnSuccess, OnFailure); StartWatcher(_settings.WatchFolder); + RestorePendingUploads(); UpdateSteps(); } @@ -251,6 +253,11 @@ public partial class MainWindow : Window return; } + if (ShouldSkipUpload(e.FullPath)) + { + return; + } + _uploadService.Enqueue(e.FullPath, OnQueued); } @@ -261,6 +268,11 @@ public partial class MainWindow : Window return; } + if (ShouldSkipUpload(e.FullPath)) + { + return; + } + _uploadService.Enqueue(e.FullPath, OnQueued); } @@ -280,6 +292,7 @@ public partial class MainWindow : Window { Interlocked.Increment(ref _queuedCount); UpdateUpload(path, UploadStatus.Queued); + AddPendingUpload(path); UpdateStatusIfAllowed($"Wartet: {Path.GetFileName(path)}", false); UpdateCountersText(); } @@ -299,6 +312,8 @@ public partial class MainWindow : Window Interlocked.Decrement(ref _uploadingCount); _lastSuccessAt = DateTimeOffset.Now; UpdateUpload(path, UploadStatus.Success); + RemovePendingUpload(path); + MarkUploaded(path); UpdateStatusIfAllowed($"Hochgeladen: {Path.GetFileName(path)}", false); UpdateCountersText(); UpdateLiveStatus(); @@ -310,6 +325,7 @@ public partial class MainWindow : Window Interlocked.Decrement(ref _uploadingCount); Interlocked.Increment(ref _failedCount); UpdateUpload(path, UploadStatus.Failed); + RemovePendingUpload(path); UpdateStatusIfAllowed($"Upload fehlgeschlagen: {Path.GetFileName(path)}", true); SetLastError($"{Path.GetFileName(path)} – {message}"); UpdateRetryButton(); @@ -398,6 +414,92 @@ public partial class MainWindow : Window ReconnectButton.IsEnabled = enabled; } + private void RestorePendingUploads() + { + EnsureSettingsCollections(); + + if (_settings.PendingUploads.Count == 0) + { + return; + } + + var pending = _settings.PendingUploads.ToList(); + var changed = false; + + foreach (var path in pending) + { + if (string.IsNullOrWhiteSpace(path) || !File.Exists(path) || ShouldSkipUpload(path)) + { + _settings.PendingUploads.Remove(path); + changed = true; + continue; + } + + _uploadService.Enqueue(path, OnQueued); + } + + if (changed) + { + _settingsStore.Save(_settings); + } + } + + private bool ShouldSkipUpload(string path) + { + var signature = GetUploadSignature(path); + if (signature is null) + { + return true; + } + + return _settings.UploadedFiles.TryGetValue(path, out var recorded) && recorded == signature; + } + + private string? GetUploadSignature(string path) + { + if (!File.Exists(path)) + { + return null; + } + + var info = new FileInfo(path); + return $"{info.Length}:{info.LastWriteTimeUtc.Ticks}"; + } + + private void AddPendingUpload(string path) + { + EnsureSettingsCollections(); + + if (!_settings.PendingUploads.Contains(path)) + { + _settings.PendingUploads.Add(path); + _settingsStore.Save(_settings); + } + } + + private void RemovePendingUpload(string path) + { + EnsureSettingsCollections(); + + if (_settings.PendingUploads.Remove(path)) + { + _settingsStore.Save(_settings); + } + } + + private void MarkUploaded(string path) + { + var signature = GetUploadSignature(path); + if (signature is null) + { + return; + } + + EnsureSettingsCollections(); + _settings.UploadedFiles[path] = signature; + _settingsStore.Save(_settings); + } + private void TitleText_PointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e) { if (e.ClickCount < 2) @@ -706,4 +808,10 @@ public partial class MainWindow : Window { return code.Length == 6 && code.All(ch => ch is >= '0' and <= '9'); } + + private void EnsureSettingsCollections() + { + _settings.PendingUploads ??= new List(); + _settings.UploadedFiles ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + } } diff --git a/clients/photobooth-uploader/PhotoboothUploader/Models/PhotoboothSettings.cs b/clients/photobooth-uploader/PhotoboothUploader/Models/PhotoboothSettings.cs index de48deb..d573d36 100644 --- a/clients/photobooth-uploader/PhotoboothUploader/Models/PhotoboothSettings.cs +++ b/clients/photobooth-uploader/PhotoboothUploader/Models/PhotoboothSettings.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; + namespace PhotoboothUploader.Models; public sealed class PhotoboothSettings @@ -9,6 +12,8 @@ public sealed class PhotoboothSettings public string? Password { get; set; } public string? ResponseFormat { get; set; } public string? WatchFolder { get; set; } + public List PendingUploads { get; set; } = new(); + public Dictionary UploadedFiles { get; set; } = new(StringComparer.OrdinalIgnoreCase); public string? LastError { get; set; } public string? LastErrorAt { get; set; } public int MaxConcurrentUploads { get; set; } = 2;