using System; using System.IO; using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Platform.Storage; using Avalonia.Threading; using PhotoboothUploader.Models; using PhotoboothUploader.Services; namespace PhotoboothUploader; public partial class MainWindow : Window { private const string DefaultBaseUrl = "https://fotospiel.app"; private PhotoboothConnectClient _client; private readonly SettingsStore _settingsStore = new(); private readonly UploadService _uploadService = new(); private PhotoboothSettings _settings; private FileSystemWatcher? _watcher; private readonly Dictionary _uploadsByPath = new(StringComparer.OrdinalIgnoreCase); private readonly HashSet _failedPaths = new(StringComparer.OrdinalIgnoreCase); public ObservableCollection RecentUploads { get; } = new(); public MainWindow() { InitializeComponent(); _settings = _settingsStore.Load(); _settings.BaseUrl ??= DefaultBaseUrl; _client = new PhotoboothConnectClient(_settings.BaseUrl); _settingsStore.Save(_settings); DataContext = this; ApplySettings(); } private async void ConnectButton_Click(object? sender, RoutedEventArgs e) { var code = (CodeBox.Text ?? string.Empty).Trim(); if (code.Length != 6 || code.Any(ch => ch is < '0' or > '9')) { StatusText.Text = "Bitte einen gültigen 6-stelligen Code eingeben."; return; } ConnectButton.IsEnabled = false; StatusText.Text = "Verbinde..."; var response = await _client.RedeemAsync(code); if (response.Data is null) { StatusText.Text = response.Message ?? "Verbindung fehlgeschlagen."; ConnectButton.IsEnabled = true; return; } _settings.UploadUrl = ResolveUploadUrl(response.Data.UploadUrl); _settings.Username = response.Data.Username; _settings.Password = response.Data.Password; _settings.ResponseFormat = response.Data.ResponseFormat; _settingsStore.Save(_settings); StatusText.Text = "Verbunden. Upload bereit."; PickFolderButton.IsEnabled = true; StartUploadPipelineIfReady(); ConnectButton.IsEnabled = true; } private async void PickFolderButton_Click(object? sender, RoutedEventArgs e) { var options = new FolderPickerOpenOptions { Title = "Upload-Ordner auswählen", AllowMultiple = false, }; var folders = await StorageProvider.OpenFolderPickerAsync(options); var folder = folders.FirstOrDefault(); var localPath = folder?.TryGetLocalPath(); if (string.IsNullOrWhiteSpace(localPath)) { return; } _settings.WatchFolder = localPath; _settingsStore.Save(_settings); FolderText.Text = localPath; StartUploadPipelineIfReady(); } private void ApplySettings() { if (!string.IsNullOrWhiteSpace(_settings.WatchFolder)) { FolderText.Text = _settings.WatchFolder; } if (!string.IsNullOrWhiteSpace(_settings.UploadUrl)) { StatusText.Text = "Verbunden. Upload bereit."; PickFolderButton.IsEnabled = true; StartUploadPipelineIfReady(); } UpdateSteps(); } private void StartUploadPipelineIfReady() { if (string.IsNullOrWhiteSpace(_settings.UploadUrl) || string.IsNullOrWhiteSpace(_settings.WatchFolder)) { UpdateSteps(); return; } _uploadService.Start(_settings, OnQueued, OnUploading, OnSuccess, OnFailure); StartWatcher(_settings.WatchFolder); UpdateSteps(); } private void StartWatcher(string folder) { _watcher?.Dispose(); _watcher = new FileSystemWatcher(folder) { IncludeSubdirectories = false, EnableRaisingEvents = true, }; _watcher.Created += OnFileChanged; _watcher.Changed += OnFileChanged; _watcher.Renamed += OnFileRenamed; } private void OnFileChanged(object sender, FileSystemEventArgs e) { if (!IsSupportedImage(e.FullPath)) { return; } _uploadService.Enqueue(e.FullPath, OnQueued); } private void OnFileRenamed(object sender, RenamedEventArgs e) { if (!IsSupportedImage(e.FullPath)) { return; } _uploadService.Enqueue(e.FullPath, OnQueued); } private bool IsSupportedImage(string path) { var extension = Path.GetExtension(path)?.ToLowerInvariant(); return extension is ".jpg" or ".jpeg" or ".png" or ".webp"; } private void UpdateStatus(string message) { Dispatcher.UIThread.Post(() => StatusText.Text = message); } private void OnQueued(string path) { UpdateUpload(path, UploadStatus.Queued); UpdateStatusIfAllowed($"Wartet: {Path.GetFileName(path)}", false); } private void OnUploading(string path) { UpdateUpload(path, UploadStatus.Uploading); UpdateStatusIfAllowed($"Upload läuft: {Path.GetFileName(path)}", false); } private void OnSuccess(string path) { _failedPaths.Remove(path); UpdateUpload(path, UploadStatus.Success); UpdateStatusIfAllowed($"Hochgeladen: {Path.GetFileName(path)}", false); } private void OnFailure(string path) { _failedPaths.Add(path); UpdateUpload(path, UploadStatus.Failed); UpdateStatusIfAllowed($"Upload fehlgeschlagen: {Path.GetFileName(path)}", true); UpdateRetryButton(); } private void UpdateUpload(string path, UploadStatus status) { Dispatcher.UIThread.Post(() => { if (!_uploadsByPath.TryGetValue(path, out var item)) { item = new UploadItem(path); _uploadsByPath[path] = item; RecentUploads.Insert(0, item); } item.Status = status; LastUploadText.Text = status == UploadStatus.Success ? $"Letzter Upload: {item.UpdatedLabel}" : LastUploadText.Text; while (RecentUploads.Count > 3) { var last = RecentUploads[^1]; _uploadsByPath.Remove(last.Path); RecentUploads.RemoveAt(RecentUploads.Count - 1); } UpdateRetryButton(); }); } private void UpdateStatusIfAllowed(string message, bool important) { var quiet = QuietToggle?.IsChecked ?? false; if (quiet && !important) { return; } UpdateStatus(message); } private void UpdateRetryButton() { RetryFailedButton.IsEnabled = _failedPaths.Count > 0; } private void RetryFailedButton_Click(object? sender, RoutedEventArgs e) { foreach (var path in _failedPaths.ToList()) { _uploadService.Enqueue(path, OnQueued); } _failedPaths.Clear(); UpdateRetryButton(); } private void UpdateSteps() { var hasCode = !string.IsNullOrWhiteSpace(_settings.UploadUrl); var hasFolder = !string.IsNullOrWhiteSpace(_settings.WatchFolder); var ready = hasCode && hasFolder; StepCodeText.Text = hasCode ? "1. Code eingeben ✓" : "1. Code eingeben"; StepFolderText.Text = hasFolder ? "2. Upload-Ordner wählen ✓" : "2. Upload-Ordner wählen"; StepReadyText.Text = ready ? "3. Upload läuft ✓" : "3. Upload läuft"; } private string? ResolveUploadUrl(string? uploadUrl) { if (string.IsNullOrWhiteSpace(uploadUrl)) { return uploadUrl; } if (Uri.TryCreate(uploadUrl, UriKind.Absolute, out _)) { return uploadUrl; } var baseUri = new Uri(_settings.BaseUrl ?? DefaultBaseUrl, UriKind.Absolute); return new Uri(baseUri, uploadUrl).ToString(); } }