diff --git a/src/CamBooth/.run/Publish CamBooth.App to folder.run.xml b/src/CamBooth/.run/Publish CamBooth.App to folder.run.xml new file mode 100644 index 0000000..f029761 --- /dev/null +++ b/src/CamBooth/.run/Publish CamBooth.App to folder.run.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/CamBooth/CamBooth.App/App.xaml.cs b/src/CamBooth/CamBooth.App/App.xaml.cs index 345a561..ec4a2e5 100644 --- a/src/CamBooth/CamBooth.App/App.xaml.cs +++ b/src/CamBooth/CamBooth.App/App.xaml.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Windows; using CamBooth.App.Core.AppSettings; @@ -51,6 +51,7 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); // Zuerst den Provider bauen, um AppSettings zu laden @@ -104,7 +105,42 @@ public partial class App : Application _serviceProvider = services.BuildServiceProvider(); + // Starte UploadQueueService beim Start + try + { + var uploadQueueService = _serviceProvider.GetRequiredService(); + uploadQueueService.Start(); + + // Scan für fehlgeschlagene Uploads beim Start + uploadQueueService.ScanAndQueueFailedUploads(); + + logger.Info("UploadQueueService initialisiert und gestartet"); + } + catch (Exception ex) + { + logger.Error($"Fehler beim Start des UploadQueueService: {ex.Message}"); + } + var mainWindow = _serviceProvider.GetRequiredService(); mainWindow.Show(); } + + protected override void OnExit(ExitEventArgs e) + { + // Stoppe UploadQueueService beim Beenden der App + try + { + var uploadQueueService = _serviceProvider?.GetService(); + if (uploadQueueService != null) + { + uploadQueueService.StopAsync().Wait(TimeSpan.FromSeconds(10)); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Fehler beim Stoppen des UploadQueueService: {ex.Message}"); + } + + base.OnExit(e); + } } \ No newline at end of file diff --git a/src/CamBooth/CamBooth.App/CamBooth.App.csproj b/src/CamBooth/CamBooth.App/CamBooth.App.csproj index 208bc17..de2493b 100644 --- a/src/CamBooth/CamBooth.App/CamBooth.App.csproj +++ b/src/CamBooth/CamBooth.App/CamBooth.App.csproj @@ -9,62 +9,67 @@ - x86 + x86 - x86 + x86 - + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - - - - + + + + - - Always - - - Always - + + Always + + + Always + - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + diff --git a/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.dev.json b/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.dev.json index 2735108..c6f4671 100644 --- a/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.dev.json +++ b/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.dev.json @@ -44,7 +44,7 @@ "ApiUrl": "https://gallery.grimma-fotobox.de", "Username": "itob", "Password": "VfVyqal&Nv8U&P", - "DefaultAlbumId": "gMM3W-Pk9mr8k57k-WU2Jz8t", + "DefaultAlbumId": "ukPbEHxdV_q1BOpIVKah7TUR", "AutoUploadEnabled": true }, "ConnectionStrings": { diff --git a/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.json b/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.json index 3fa6783..a994c1b 100644 --- a/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.json +++ b/src/CamBooth/CamBooth.App/Core/AppSettings/app.settings.json @@ -44,8 +44,8 @@ "ApiUrl": "https://gallery.grimma-fotobox.de", "Username": "itob", "Password": "VfVyqal&Nv8U&P", - "DefaultAlbumId": "gMM3W-Pk9mr8k57k-WU2Jz8t", - "AutoUploadEnabled": false + "DefaultAlbumId": "ukPbEHxdV_q1BOpIVKah7TUR", + "AutoUploadEnabled": true }, "ConnectionStrings": { "DefaultConnection": "Server=myServer;Database=myDB;User Id=myUser;Password=myPassword;" diff --git a/src/CamBooth/CamBooth.App/Core/GenericOverlayWindow.xaml b/src/CamBooth/CamBooth.App/Core/GenericOverlayWindow.xaml index 5498f61..3bd6480 100644 --- a/src/CamBooth/CamBooth.App/Core/GenericOverlayWindow.xaml +++ b/src/CamBooth/CamBooth.App/Core/GenericOverlayWindow.xaml @@ -6,9 +6,19 @@ xmlns:local="clr-namespace:CamBooth.App.Core" mc:Ignorable="d" Title="GenericOverlayWindow" Height="350" Width="600"> + + + Hoppla Sorry, da ging was schief! - + diff --git a/src/CamBooth/CamBooth.App/Core/Logging/Logger.cs b/src/CamBooth/CamBooth.App/Core/Logging/Logger.cs index 439998f..d1636e5 100644 --- a/src/CamBooth/CamBooth.App/Core/Logging/Logger.cs +++ b/src/CamBooth/CamBooth.App/Core/Logging/Logger.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -68,10 +68,16 @@ public class Logger : IDisposable public Logger(IConfiguration configuration) { var logLevel = configuration["LoggingSettings:LogLevel"] ?? "Information"; - var logDirectory = configuration["LoggingSettings:LogDirectory"] ?? "Logs"; + var logDirectoryInput = configuration["LoggingSettings:LogDirectory"] ?? "Logs"; var remoteServerUrl = configuration["LoggingSettings:RemoteServerUrl"]; var remoteServerApiKey = configuration["LoggingSettings:RemoteServerApiKey"]; + // Konvertiere zu absolutem Pfad + var logDirectory = Path.IsPathRooted(logDirectoryInput) + ? logDirectoryInput + : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logDirectoryInput); + + // Erstelle Verzeichnis, falls nicht vorhanden if (!Directory.Exists(logDirectory)) { Directory.CreateDirectory(logDirectory); diff --git a/src/CamBooth/CamBooth.App/Features/Camera/CameraService.cs b/src/CamBooth/CamBooth.App/Features/Camera/CameraService.cs index 0cfcf23..bc6766d 100644 --- a/src/CamBooth/CamBooth.App/Features/Camera/CameraService.cs +++ b/src/CamBooth/CamBooth.App/Features/Camera/CameraService.cs @@ -22,6 +22,8 @@ public class CameraService : IDisposable private readonly LycheeUploadService _lycheeUploadService; + private readonly UploadQueueService _uploadQueueService; + private readonly ICanonAPI _APIHandler; private CameraValue[] AvList; @@ -47,6 +49,7 @@ public class CameraService : IDisposable AppSettingsService appSettings, PictureGalleryService pictureGalleryService, LycheeUploadService lycheeUploadService, + UploadQueueService uploadQueueService, ICamera mainCamera, ICanonAPI APIHandler) { @@ -54,6 +57,7 @@ public class CameraService : IDisposable this._appSettings = appSettings; this._pictureGalleryService = pictureGalleryService; this._lycheeUploadService = lycheeUploadService; + this._uploadQueueService = uploadQueueService; this._mainCamera = mainCamera; this._APIHandler = APIHandler; try @@ -89,21 +93,43 @@ public class CameraService : IDisposable try { this.RefreshCamera(); - List cameraList = this._APIHandler.GetCameraList(); - if (cameraList.Any()) + + // Retry logic for camera detection (some systems need time to initialize) + int maxRetries = 3; + int retryDelay = 500; // milliseconds + + for (int attempt = 0; attempt < maxRetries; attempt++) { - this.OpenSession(); - this.SetSettingSaveToComputer(); - this.StarLiveView(); + if (this.CamList != null && this.CamList.Any()) + { + break; + } + + if (attempt < maxRetries - 1) + { + this._logger.Info($"No cameras found on attempt {attempt + 1}/{maxRetries}, retrying in {retryDelay}ms..."); + System.Threading.Thread.Sleep(retryDelay); + this.RefreshCamera(); + } } - else + + if (this.CamList == null || !this.CamList.Any()) { this.ReportError("No cameras / devices found"); - return; + throw new InvalidOperationException("No cameras / devices found after multiple attempts"); } - string cameraDeviceNames = string.Join(", ", cameraList.Select(cam => cam.DeviceName)); + this._logger.Info($"Found {this.CamList.Count} camera(s)"); + string cameraDeviceNames = string.Join(", ", this.CamList.Select(cam => cam.DeviceName)); this._logger.Info(cameraDeviceNames); + + // Update _mainCamera reference to the freshly detected camera + this._mainCamera = this.CamList[0]; + this._logger.Info($"Selected camera: {this._mainCamera.DeviceName}"); + + this.OpenSession(); + this.SetSettingSaveToComputer(); + this.StarLiveView(); } catch (Exception ex) { @@ -161,13 +187,11 @@ public class CameraService : IDisposable { try { - if (this.CamList == null || this.CamList.Count == 0) + if (this._mainCamera == null) { - throw new InvalidOperationException("No cameras available in camera list"); + throw new InvalidOperationException("Camera reference is null. Make sure ConnectCamera is called first."); } - this._mainCamera = this.CamList[0]; - // Check if session is already open if (this._mainCamera.SessionOpen) { @@ -377,34 +401,9 @@ public class CameraService : IDisposable this._pictureGalleryService.LoadThumbnailsToCache(); }); - // Auto-Upload zu Lychee, falls aktiviert - if (this._appSettings.LycheeAutoUploadEnabled) - { - this._logger.Info("Auto-Upload aktiviert. Starte Upload zu Lychee..."); - - // Upload im Hintergrund, damit die Fotobox nicht blockiert wird - _ = Task.Run(async () => - { - try - { - var albumId = this._appSettings.LycheeDefaultAlbumId; - var uploadSuccess = await this._lycheeUploadService.UploadImageAsync(savedPhotoPath, albumId); - - if (uploadSuccess) - { - this._logger.Info($"✅ Lychee-Upload erfolgreich: {Info.FileName}"); - } - else - { - this._logger.Warning($"⚠️ Lychee-Upload fehlgeschlagen: {Info.FileName}"); - } - } - catch (Exception ex) - { - this._logger.Error($"❌ Fehler beim Lychee-Upload: {ex.Message}"); - } - }); - } + // Füge neues Foto zur Upload-Queue hinzu (wenn Auto-Upload aktiviert) + this._uploadQueueService.QueueNewPhoto(savedPhotoPath); + this._logger.Info($"Foto zur Upload-Queue hinzugefügt: {Info.FileName}"); } catch (Exception ex) { diff --git a/src/CamBooth/CamBooth.App/Features/LycheeUpload/LycheeUploadPage.xaml b/src/CamBooth/CamBooth.App/Features/LycheeUpload/LycheeUploadPage.xaml index bbe7d6c..d383a79 100644 --- a/src/CamBooth/CamBooth.App/Features/LycheeUpload/LycheeUploadPage.xaml +++ b/src/CamBooth/CamBooth.App/Features/LycheeUpload/LycheeUploadPage.xaml @@ -75,11 +75,9 @@ x:Name="ConnectButton" Content="Verbinden" Click="ConnectButton_Click" + Style="{StaticResource PrimaryActionButtonStyle}" Width="140" - Height="45" - Background="#D4AF37" - Foreground="#1F1A00" - FontWeight="SemiBold" /> + Height="45" /> @@ -165,18 +163,16 @@ + Margin="0,0,10,0" /> + Height="45" /> diff --git a/src/CamBooth/CamBooth.App/Features/LycheeUpload/LycheeUploadService.cs b/src/CamBooth/CamBooth.App/Features/LycheeUpload/LycheeUploadService.cs index 6fe8365..44e3f3f 100644 --- a/src/CamBooth/CamBooth.App/Features/LycheeUpload/LycheeUploadService.cs +++ b/src/CamBooth/CamBooth.App/Features/LycheeUpload/LycheeUploadService.cs @@ -1,10 +1,11 @@ -using System.IO; +using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; +using System.Windows.Media.Imaging; using CamBooth.App.Core.AppSettings; using CamBooth.App.Core.Logging; @@ -18,6 +19,7 @@ public class LycheeUploadService : IDisposable private readonly AppSettingsService _appSettings; private readonly Logger _logger; private readonly HttpClient _httpClient; + private readonly QRCodeGenerationService _qrCodeGenerationService; private readonly CookieContainer _cookieContainer; private readonly HttpClientHandler _httpClientHandler; private string? _csrfToken; @@ -27,6 +29,7 @@ public class LycheeUploadService : IDisposable { _appSettings = appSettings; _logger = logger; + _qrCodeGenerationService = new QRCodeGenerationService(logger); // CookieContainer für Session-Management _cookieContainer = new CookieContainer(); @@ -47,6 +50,11 @@ public class LycheeUploadService : IDisposable /// public bool IsAuthenticated => _isAuthenticated; + /// + /// Der zuletzt generierte QR-Code + /// + public BitmapImage? LastGeneratedQRCode { get; private set; } + /// /// Authentifiziert sich bei Lychee /// @@ -532,6 +540,52 @@ public class LycheeUploadService : IDisposable } } + /// + /// Generiert einen QR-Code für die Lychee-Galerie-URL + /// + /// BitmapImage des QR-Codes oder null bei Fehler + public BitmapImage? GenerateGalleryQRCode() + { + try + { + var lycheeUrl = _appSettings.LycheeApiUrl; + var albumId = _appSettings.LycheeDefaultAlbumId; + + if (string.IsNullOrEmpty(lycheeUrl)) + { + _logger.Error("Lychee-URL ist nicht konfiguriert. Kann QR-Code nicht generieren."); + return null; + } + + if (string.IsNullOrEmpty(albumId)) + { + _logger.Warning("Lychee DefaultAlbumId ist nicht konfiguriert. QR-Code zeigt nur die Basis-URL."); + } + + _logger.Info("Generiere QR-Code für Lychee-Galerie..."); + + // Konstruiere die Gallery-URL: ApiUrl + /Gallery + DefaultAlbumId + var galleryUrl = $"{lycheeUrl}/gallery/{albumId}"; + _logger.Debug($"QR-Code URL: {galleryUrl}"); + + // Generiere QR-Code mit der Gallery-URL + var qrCode = _qrCodeGenerationService.GenerateQRCode(galleryUrl); + + if (qrCode != null) + { + LastGeneratedQRCode = qrCode; + _logger.Info("✅ QR-Code erfolgreich generiert"); + } + + return qrCode; + } + catch (Exception ex) + { + _logger.Error($"Fehler beim Generieren des QR-Codes: {ex.Message}"); + return null; + } + } + public void Dispose() { _httpClient?.Dispose(); diff --git a/src/CamBooth/CamBooth.App/Features/LycheeUpload/QRCodeDisplayWindow.xaml b/src/CamBooth/CamBooth.App/Features/LycheeUpload/QRCodeDisplayWindow.xaml new file mode 100644 index 0000000..c93b05d --- /dev/null +++ b/src/CamBooth/CamBooth.App/Features/LycheeUpload/QRCodeDisplayWindow.xaml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + +