Refactor: Simplify CameraService, improve session handling, and streamline application initialization
This commit is contained in:
parent
91935cd41c
commit
b3c91da331
@ -1,4 +1,3 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
using CamBooth.App.Core.AppSettings;
|
using CamBooth.App.Core.AppSettings;
|
||||||
@ -21,134 +20,113 @@ namespace CamBooth.App;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
private IServiceProvider _serviceProvider;
|
private IServiceProvider? _serviceProvider;
|
||||||
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
|
||||||
// Konfiguration laden
|
var configuration = BuildConfiguration();
|
||||||
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
|
|
||||||
|
|
||||||
var configBuilder = new ConfigurationBuilder()
|
|
||||||
.SetBasePath(AppContext.BaseDirectory)
|
|
||||||
.AddJsonFile("Core/AppSettings/app.settings.json", optional: false, reloadOnChange: true);
|
|
||||||
|
|
||||||
if (environment == "Development")
|
|
||||||
{
|
|
||||||
configBuilder.AddJsonFile("Core/AppSettings/app.settings.dev.json", optional: true, reloadOnChange: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
var configuration = configBuilder.Build();
|
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
services.AddSingleton<IConfiguration>(configuration);
|
RegisterServices(services, configuration);
|
||||||
|
|
||||||
services.AddSingleton<Logger>();
|
|
||||||
services.AddSingleton<AppSettingsService>();
|
|
||||||
services.AddSingleton<PictureGalleryService>();
|
|
||||||
services.AddSingleton<CameraService>();
|
|
||||||
|
|
||||||
services.AddSingleton<PhotoPrismAuthService>();
|
|
||||||
services.AddSingleton<PhotoPrismUploadService>();
|
|
||||||
services.AddSingleton<PhotoPrismUploadQueueService>();
|
|
||||||
|
|
||||||
var tempProvider = services.BuildServiceProvider();
|
|
||||||
var appSettings = tempProvider.GetRequiredService<AppSettingsService>();
|
|
||||||
var logger = tempProvider.GetRequiredService<Logger>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(appSettings.PictureLocation))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(appSettings.PictureLocation);
|
|
||||||
logger.Debug($"Picture directory created: {appSettings.PictureLocation}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.Error($"Failed to create picture directory: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jetzt die Camera Services basierend auf AppSettings registrieren
|
|
||||||
// Mit Try-Catch für fehlende DLL-Abhängigkeiten
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (appSettings.UseMockCamera)
|
|
||||||
{
|
|
||||||
services.AddSingleton<ICanonAPI, CanonAPIMock>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
services.AddSingleton<ICanonAPI, CanonAPI>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (DllNotFoundException ex)
|
|
||||||
{
|
|
||||||
// Falls EDSDK DLL nicht gefunden, fallback auf Mock
|
|
||||||
MessageBox.Show(
|
|
||||||
$"EDSDK konnte nicht geladen werden. Verwende Mock-Kamera.\n\nFehler: {ex.Message}",
|
|
||||||
"DLL nicht gefunden",
|
|
||||||
MessageBoxButton.OK,
|
|
||||||
MessageBoxImage.Warning);
|
|
||||||
|
|
||||||
services.AddSingleton<ICanonAPI, CanonAPIMock>();
|
|
||||||
}
|
|
||||||
|
|
||||||
services.AddTransient<MainWindow>();
|
|
||||||
|
|
||||||
_serviceProvider = services.BuildServiceProvider();
|
_serviceProvider = services.BuildServiceProvider();
|
||||||
|
|
||||||
// Starte PhotoPrism Upload-Service beim Start
|
StartBackgroundServices();
|
||||||
try
|
|
||||||
{
|
|
||||||
var uploadQueueService = _serviceProvider.GetRequiredService<PhotoPrismUploadQueueService>();
|
|
||||||
uploadQueueService.Start();
|
|
||||||
|
|
||||||
// Scan für fehlgeschlagene Uploads beim Start
|
_serviceProvider.GetRequiredService<MainWindow>().Show();
|
||||||
uploadQueueService.ScanAndQueueFailedUploads();
|
|
||||||
|
|
||||||
logger.Info("PhotoPrism UploadQueueService initialisiert und gestartet");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.Error($"Fehler beim Start des PhotoPrism UploadQueueService: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var mainWindow = _serviceProvider.GetRequiredService<MainWindow>();
|
|
||||||
mainWindow.Show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnExit(ExitEventArgs e)
|
protected override void OnExit(ExitEventArgs e)
|
||||||
{
|
{
|
||||||
// Stoppe PhotoPrism UploadQueueService beim Beenden der App
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var uploadQueueService = _serviceProvider?.GetService<PhotoPrismUploadQueueService>();
|
_serviceProvider?.GetService<PhotoPrismUploadQueueService>()
|
||||||
if (uploadQueueService != null)
|
?.StopAsync().Wait(TimeSpan.FromSeconds(10));
|
||||||
{
|
|
||||||
uploadQueueService.StopAsync().Wait(TimeSpan.FromSeconds(10));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Fehler beim Stoppen des PhotoPrism UploadQueueService: {ex.Message}");
|
System.Diagnostics.Debug.WriteLine($"Error stopping upload service: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispose Service Provider, damit IDisposable-Services (z.B. CameraService) sauber beendet werden.
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_serviceProvider is IDisposable disposableProvider)
|
(_serviceProvider as IDisposable)?.Dispose();
|
||||||
{
|
|
||||||
disposableProvider.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Fehler beim Dispose des ServiceProviders: {ex.Message}");
|
System.Diagnostics.Debug.WriteLine($"Error disposing service provider: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnExit(e);
|
base.OnExit(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static IConfiguration BuildConfiguration()
|
||||||
|
{
|
||||||
|
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
|
||||||
|
|
||||||
|
var builder = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(AppContext.BaseDirectory)
|
||||||
|
.AddJsonFile("Core/AppSettings/app.settings.json", optional: false, reloadOnChange: true);
|
||||||
|
|
||||||
|
if (environment == "Development")
|
||||||
|
builder.AddJsonFile("Core/AppSettings/app.settings.dev.json", optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void RegisterServices(ServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddSingleton(configuration);
|
||||||
|
services.AddSingleton<Logger>();
|
||||||
|
services.AddSingleton<AppSettingsService>();
|
||||||
|
services.AddSingleton<PictureGalleryService>();
|
||||||
|
services.AddSingleton<CameraService>();
|
||||||
|
services.AddSingleton<PhotoPrismAuthService>();
|
||||||
|
services.AddSingleton<PhotoPrismUploadService>();
|
||||||
|
services.AddSingleton<PhotoPrismUploadQueueService>();
|
||||||
|
services.AddSingleton<MainWindowViewModel>();
|
||||||
|
services.AddTransient<MainWindow>();
|
||||||
|
|
||||||
|
RegisterCameraApi(services, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void RegisterCameraApi(ServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var useMockCamera = bool.Parse(configuration["AppSettings:UseMockCamera"] ?? "false");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICanonAPI canonApi = useMockCamera ? new CanonAPIMock() : new CanonAPI();
|
||||||
|
services.AddSingleton(canonApi);
|
||||||
|
}
|
||||||
|
catch (DllNotFoundException ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
$"EDSDK konnte nicht geladen werden. Verwende Mock-Kamera.\n\nFehler: {ex.Message}",
|
||||||
|
"DLL nicht gefunden", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
|
||||||
|
services.AddSingleton<ICanonAPI>(new CanonAPIMock());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void StartBackgroundServices()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logger = _serviceProvider!.GetRequiredService<Logger>();
|
||||||
|
var uploadQueueService = _serviceProvider.GetRequiredService<PhotoPrismUploadQueueService>();
|
||||||
|
uploadQueueService.Start();
|
||||||
|
uploadQueueService.ScanAndQueueFailedUploads();
|
||||||
|
logger.Info("PhotoPrism UploadQueueService gestartet");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Error starting background services: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,274 +15,115 @@ namespace CamBooth.App.Features.Camera;
|
|||||||
public class CameraService : IDisposable
|
public class CameraService : IDisposable
|
||||||
{
|
{
|
||||||
private readonly AppSettingsService _appSettings;
|
private readonly AppSettingsService _appSettings;
|
||||||
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
private readonly PictureGalleryService _pictureGalleryService;
|
private readonly PictureGalleryService _pictureGalleryService;
|
||||||
|
|
||||||
private readonly PhotoPrismUploadQueueService _photoPrismUploadQueueService;
|
private readonly PhotoPrismUploadQueueService _photoPrismUploadQueueService;
|
||||||
|
private readonly ICanonAPI _canonApi;
|
||||||
|
|
||||||
private readonly ICanonAPI _APIHandler;
|
private ICamera? _mainCamera;
|
||||||
|
private List<ICamera>? _camList;
|
||||||
|
private bool _isConnected;
|
||||||
|
|
||||||
private CameraValue[] AvList;
|
/// <summary>Fires whenever the camera delivers a new live-view frame.</summary>
|
||||||
|
public event Action<Stream>? LiveViewUpdated;
|
||||||
|
|
||||||
private int BulbTime = 30;
|
public bool IsConnected => _isConnected && _mainCamera?.SessionOpen == true;
|
||||||
|
|
||||||
private List<ICamera> CamList;
|
|
||||||
|
|
||||||
private int ErrCount;
|
|
||||||
|
|
||||||
private object ErrLock = new();
|
|
||||||
|
|
||||||
private bool IsInit;
|
|
||||||
|
|
||||||
private CameraValue[] ISOList;
|
|
||||||
|
|
||||||
public ICamera? _mainCamera;
|
|
||||||
|
|
||||||
private CameraValue[] TvList;
|
|
||||||
|
|
||||||
|
|
||||||
public CameraService(Logger logger,
|
public CameraService(
|
||||||
|
Logger logger,
|
||||||
AppSettingsService appSettings,
|
AppSettingsService appSettings,
|
||||||
PictureGalleryService pictureGalleryService,
|
PictureGalleryService pictureGalleryService,
|
||||||
PhotoPrismUploadQueueService photoPrismUploadQueueService,
|
PhotoPrismUploadQueueService photoPrismUploadQueueService,
|
||||||
ICanonAPI APIHandler)
|
ICanonAPI canonApi)
|
||||||
{
|
{
|
||||||
this._logger = logger;
|
_logger = logger;
|
||||||
this._appSettings = appSettings;
|
_appSettings = appSettings;
|
||||||
this._pictureGalleryService = pictureGalleryService;
|
_pictureGalleryService = pictureGalleryService;
|
||||||
this._photoPrismUploadQueueService = photoPrismUploadQueueService;
|
_photoPrismUploadQueueService = photoPrismUploadQueueService;
|
||||||
this._APIHandler = APIHandler;
|
_canonApi = canonApi;
|
||||||
try
|
|
||||||
{
|
|
||||||
this.IsInit = true;
|
|
||||||
}
|
|
||||||
catch (DllNotFoundException)
|
|
||||||
{
|
|
||||||
this.ReportError("Canon DLLs not found!");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.ReportError(ex.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary> Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. </summary>
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.CloseSession();
|
CloseSession();
|
||||||
this.IsInit = false;
|
_canonApi.Dispose();
|
||||||
this._APIHandler.Dispose();
|
_mainCamera?.Dispose();
|
||||||
this._mainCamera?.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void ConnectCamera()
|
public void ConnectCamera()
|
||||||
{
|
{
|
||||||
ErrorHandler.SevereErrorHappened += this.ErrorHandler_SevereErrorHappened;
|
ErrorHandler.SevereErrorHappened += ErrorHandler_SevereErrorHappened;
|
||||||
ErrorHandler.NonSevereErrorHappened += this.ErrorHandler_NonSevereErrorHappened;
|
ErrorHandler.NonSevereErrorHappened += ErrorHandler_NonSevereErrorHappened;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.RefreshCamera();
|
RefreshCameraList();
|
||||||
|
|
||||||
// Retry logic for camera detection (some systems need time to initialize)
|
const int maxRetries = 3;
|
||||||
int maxRetries = 3;
|
const int retryDelayMs = 750;
|
||||||
int retryDelay = 750; // milliseconds
|
|
||||||
|
|
||||||
for (int attempt = 0; attempt < maxRetries; attempt++)
|
for (int attempt = 0; attempt < maxRetries; attempt++)
|
||||||
{
|
{
|
||||||
if (this.CamList != null && this.CamList.Any())
|
if (_camList?.Any() == true) break;
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attempt < maxRetries - 1)
|
if (attempt < maxRetries - 1)
|
||||||
{
|
{
|
||||||
System.Threading.Thread.Sleep(retryDelay);
|
System.Threading.Thread.Sleep(retryDelayMs);
|
||||||
this.RefreshCamera();
|
RefreshCameraList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.CamList == null || !this.CamList.Any())
|
if (_camList?.Any() != true)
|
||||||
{
|
throw new InvalidOperationException("No cameras found after multiple attempts.");
|
||||||
this.ReportError("No cameras / devices found");
|
|
||||||
throw new InvalidOperationException("No cameras / devices found after multiple attempts");
|
|
||||||
}
|
|
||||||
|
|
||||||
string cameraDeviceNames = string.Join(", ", this.CamList.Select(cam => cam.DeviceName));
|
_mainCamera = _camList[0];
|
||||||
this._logger.Debug(cameraDeviceNames);
|
_logger.Info($"Camera found: {_mainCamera.DeviceName}");
|
||||||
|
|
||||||
// Update _mainCamera reference to the freshly detected camera
|
OpenSession();
|
||||||
this._mainCamera = this.CamList[0];
|
SetSaveToComputer();
|
||||||
this._logger.Info($"Selected camera: {this._mainCamera.DeviceName}");
|
StartLiveView();
|
||||||
|
_isConnected = true;
|
||||||
this.OpenSession();
|
|
||||||
this.SetSettingSaveToComputer();
|
|
||||||
this.StarLiveView();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this._logger.Error($"Error connecting camera: {ex.Message}");
|
_logger.Error($"Error connecting camera: {ex.Message}");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void SetSettingSaveToComputer()
|
|
||||||
{
|
|
||||||
this._mainCamera.SetSetting(PropertyID.SaveTo, (int)SaveTo.Host);
|
|
||||||
this._mainCamera.SetCapacity(4096, int.MaxValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void CloseSession()
|
public void CloseSession()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (this._mainCamera != null && this._mainCamera.SessionOpen)
|
if (_mainCamera?.SessionOpen == true)
|
||||||
{
|
{
|
||||||
this._mainCamera.CloseSession();
|
_mainCamera.CloseSession();
|
||||||
this._logger.Info("Camera session closed");
|
_logger.Info("Camera session closed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this._logger.Error($"Error closing camera session: {ex.Message}");
|
_logger.Error($"Error closing camera session: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvCoBox.Items.Clear();
|
_isConnected = false;
|
||||||
// TvCoBox.Items.Clear();
|
|
||||||
// ISOCoBox.Items.Clear();
|
|
||||||
// SettingsGroupBox.IsEnabled = false;
|
|
||||||
// LiveViewGroupBox.IsEnabled = false;
|
|
||||||
// SessionButton.Content = "Open Session";
|
|
||||||
// SessionLabel.Content = "No open session";
|
|
||||||
// StarLVButton.Content = "Start LV";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void RefreshCamera()
|
|
||||||
{
|
|
||||||
// CameraListBox.Items.Clear();
|
|
||||||
this.CamList = this._APIHandler.GetCameraList();
|
|
||||||
|
|
||||||
// foreach (Camera cam in CamList) CameraListBox.Items.Add(cam.DeviceName);
|
|
||||||
// if (_mainCamera?.SessionOpen == true) CameraListBox.SelectedIndex = CamList.FindIndex(t => t.ID == _mainCamera.ID);
|
|
||||||
// else if (CamList.Count > 0) CameraListBox.SelectedIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void OpenSession()
|
|
||||||
{
|
|
||||||
if (this._mainCamera == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Camera reference is null. Make sure ConnectCamera is called first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._mainCamera.SessionOpen)
|
|
||||||
{
|
|
||||||
this._logger.Info($"Camera session already open for {this._mainCamera.DeviceName}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._logger.Info($"Opening session for camera: {this._mainCamera.DeviceName}");
|
|
||||||
|
|
||||||
const int maxRetries = 3;
|
|
||||||
const int retryDelayMs = 1000;
|
|
||||||
|
|
||||||
for (int attempt = 0; attempt < maxRetries; attempt++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this._mainCamera.OpenSession();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (attempt < maxRetries - 1 && IsSessionNotOpenError(ex))
|
|
||||||
{
|
|
||||||
this._logger.Warning($"OpenSession attempt {attempt + 1}/{maxRetries} failed ({ex.Message}), refreshing camera and retrying in {retryDelayMs}ms...");
|
|
||||||
System.Threading.Thread.Sleep(retryDelayMs);
|
|
||||||
this.RefreshCamera();
|
|
||||||
if (this.CamList?.Any() == true)
|
|
||||||
{
|
|
||||||
this._mainCamera = this.CamList[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this._logger.Error($"Failed to open camera session: {ex.Message}");
|
|
||||||
this.ReportError($"Failed to open camera session: {ex.Message}");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._logger.Info("Camera session opened successfully");
|
|
||||||
|
|
||||||
//_mainCamera.ProgressChanged += MainCamera_ProgressChanged;
|
|
||||||
this._mainCamera.StateChanged += this.MainCamera_StateChanged;
|
|
||||||
this._mainCamera.DownloadReady += this.MainCamera_DownloadReady;
|
|
||||||
|
|
||||||
this.AvList = this._mainCamera.GetSettingsList(PropertyID.Av);
|
|
||||||
this.TvList = this._mainCamera.GetSettingsList(PropertyID.Tv);
|
|
||||||
this.ISOList = this._mainCamera.GetSettingsList(PropertyID.ISO);
|
|
||||||
// ISOCoBox.SelectedIndex = ISOCoBox.Items.IndexOf(ISOValues.GetValue(_mainCamera.GetInt32Setting(PropertyID.ISO)).StringValue);
|
|
||||||
// SettingsGroupBox.IsEnabled = true;
|
|
||||||
// LiveViewGroupBox.IsEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void ReportError(string message)
|
|
||||||
{
|
|
||||||
this._logger.Info(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static bool IsSessionNotOpenError(Exception ex)
|
|
||||||
{
|
|
||||||
const string errorName = "SESSION_NOT_OPEN";
|
|
||||||
return ex.Message.Contains(errorName) || (ex.InnerException?.Message?.Contains(errorName) ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void StarLiveView()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!this._mainCamera.IsLiveViewOn)
|
|
||||||
{
|
|
||||||
this._mainCamera.StartLiveView();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this._mainCamera.StopLiveView();
|
|
||||||
|
|
||||||
//LVCanvas.Background = Brushes.LightGray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.ReportError(ex.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void TakePhoto()
|
public void TakePhoto()
|
||||||
{
|
{
|
||||||
try
|
if (_mainCamera == null) throw new InvalidOperationException("Camera not connected.");
|
||||||
{
|
_mainCamera.TakePhoto();
|
||||||
this._mainCamera.TakePhoto();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.ReportError(ex.Message);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task PrepareFocusAsync(int focusTimeoutMs = 1500)
|
public async Task PrepareFocusAsync(int focusTimeoutMs = 1500)
|
||||||
{
|
{
|
||||||
if (this._mainCamera is not EOSDigital.API.Camera sdkCamera)
|
if (_mainCamera is not EOSDigital.API.Camera sdkCamera)
|
||||||
{
|
{
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
return;
|
return;
|
||||||
@ -293,25 +134,21 @@ public class CameraService : IDisposable
|
|||||||
void FocusStateChanged(EOSDigital.API.Camera sender, StateEventID eventId, int parameter)
|
void FocusStateChanged(EOSDigital.API.Camera sender, StateEventID eventId, int parameter)
|
||||||
{
|
{
|
||||||
if (eventId == StateEventID.AfResult)
|
if (eventId == StateEventID.AfResult)
|
||||||
{
|
|
||||||
focusCompleted.TrySetResult(true);
|
focusCompleted.TrySetResult(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sdkCamera.StateChanged += FocusStateChanged;
|
sdkCamera.StateChanged += FocusStateChanged;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Halfway));
|
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Halfway));
|
||||||
var completedTask = await Task.WhenAny(focusCompleted.Task, Task.Delay(focusTimeoutMs));
|
var completed = await Task.WhenAny(focusCompleted.Task, Task.Delay(focusTimeoutMs));
|
||||||
if (completedTask != focusCompleted.Task)
|
if (completed != focusCompleted.Task)
|
||||||
{
|
_logger.Info("Autofocus timeout reached, continuing.");
|
||||||
this._logger.Info("Autofocus timeout reached, continuing with countdown.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.ReportError(ex.Message);
|
_logger.Error(ex.Message);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -319,120 +156,142 @@ public class CameraService : IDisposable
|
|||||||
{
|
{
|
||||||
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF));
|
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex) { _logger.Error(ex.Message); }
|
||||||
{
|
|
||||||
this.ReportError(ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
sdkCamera.StateChanged -= FocusStateChanged;
|
sdkCamera.StateChanged -= FocusStateChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#region API Events
|
private void RefreshCameraList() => _camList = _canonApi.GetCameraList();
|
||||||
|
|
||||||
// private void APIHandler_CameraAdded(CanonAPI sender)
|
|
||||||
// {
|
private void OpenSession()
|
||||||
// try
|
{
|
||||||
// {
|
if (_mainCamera == null)
|
||||||
// }
|
throw new InvalidOperationException("Camera reference is null.");
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
if (_mainCamera.SessionOpen)
|
||||||
// ReportError(ex.Message, false);
|
{
|
||||||
// }
|
_logger.Info($"Session already open for {_mainCamera.DeviceName}");
|
||||||
// }
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Info($"Opening session for: {_mainCamera.DeviceName}");
|
||||||
|
|
||||||
|
const int maxRetries = 3;
|
||||||
|
const int retryDelayMs = 1000;
|
||||||
|
|
||||||
|
for (int attempt = 0; attempt < maxRetries; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_mainCamera.OpenSession();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (attempt < maxRetries - 1 && IsSessionNotOpenError(ex))
|
||||||
|
{
|
||||||
|
_logger.Warning($"OpenSession attempt {attempt + 1}/{maxRetries} failed, retrying...");
|
||||||
|
System.Threading.Thread.Sleep(retryDelayMs);
|
||||||
|
RefreshCameraList();
|
||||||
|
if (_camList?.Any() == true)
|
||||||
|
_mainCamera = _camList[0];
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error($"Failed to open session: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Info("Session opened successfully");
|
||||||
|
_mainCamera.StateChanged += MainCamera_StateChanged;
|
||||||
|
_mainCamera.DownloadReady += MainCamera_DownloadReady;
|
||||||
|
_mainCamera.LiveViewUpdated += MainCamera_LiveViewUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void SetSaveToComputer()
|
||||||
|
{
|
||||||
|
_mainCamera!.SetSetting(PropertyID.SaveTo, (int)SaveTo.Host);
|
||||||
|
_mainCamera.SetCapacity(4096, int.MaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void StartLiveView()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_mainCamera!.IsLiveViewOn)
|
||||||
|
_mainCamera.StartLiveView();
|
||||||
|
else
|
||||||
|
_mainCamera.StopLiveView();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool IsSessionNotOpenError(Exception ex)
|
||||||
|
{
|
||||||
|
const string errorName = "SESSION_NOT_OPEN";
|
||||||
|
return ex.Message.Contains(errorName) || (ex.InnerException?.Message?.Contains(errorName) ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Camera event handlers
|
||||||
|
|
||||||
|
private void MainCamera_LiveViewUpdated(ICamera sender, Stream img) =>
|
||||||
|
LiveViewUpdated?.Invoke(img);
|
||||||
|
|
||||||
|
|
||||||
private void MainCamera_StateChanged(EOSDigital.API.Camera sender, StateEventID eventID, int parameter)
|
private void MainCamera_StateChanged(EOSDigital.API.Camera sender, StateEventID eventID, int parameter)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (eventID == StateEventID.Shutdown && this.IsInit)
|
if (eventID == StateEventID.Shutdown && _isConnected)
|
||||||
{
|
Application.Current.Dispatcher.Invoke(CloseSession);
|
||||||
Application.Current.Dispatcher.Invoke(() => this.CloseSession());
|
|
||||||
|
|
||||||
//Dispatcher.Invoke((Action)delegate { CloseSession(); });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.ReportError(ex.Message);
|
_logger.Error(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// private void MainCamera_ProgressChanged(object sender, int progress)
|
private void MainCamera_DownloadReady(ICamera sender, IDownloadInfo info)
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// //MainProgressBar.Dispatcher.Invoke((Action)delegate { MainProgressBar.Value = progress; });
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// ReportError(ex.Message, false);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private void MainCamera_LiveViewUpdated(EOSDigital.API.Camera sender, Stream img)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// using (WrapStream s = new WrapStream(img))
|
|
||||||
// {
|
|
||||||
// img.Position = 0;
|
|
||||||
// BitmapImage EvfImage = new BitmapImage();
|
|
||||||
// EvfImage.BeginInit();
|
|
||||||
// EvfImage.StreamSource = s;
|
|
||||||
// EvfImage.CacheOption = BitmapCacheOption.OnLoad;
|
|
||||||
// EvfImage.EndInit();
|
|
||||||
// EvfImage.Freeze();
|
|
||||||
// Application.Current.Dispatcher.BeginInvoke(SetImageAction, EvfImage);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// ReportError(ex.Message, false);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
private void MainCamera_DownloadReady(ICamera sender, IDownloadInfo Info)
|
|
||||||
{
|
{
|
||||||
this._logger.Info("MainCamera_DownloadReady called");
|
_logger.Info("Download ready");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Info.FileName = $"img_{Guid.NewGuid().ToString()}.jpg";
|
info.FileName = $"img_{Guid.NewGuid()}.jpg";
|
||||||
sender.DownloadFile(Info, this._appSettings.PictureLocation);
|
sender.DownloadFile(info, _appSettings.PictureLocation);
|
||||||
var savedPhotoPath = Path.Combine(this._appSettings.PictureLocation, Info.FileName);
|
var savedPath = Path.Combine(_appSettings.PictureLocation!, info.FileName);
|
||||||
this._logger.Info("Download complete: " + savedPhotoPath);
|
_logger.Info($"Download complete: {savedPath}");
|
||||||
|
|
||||||
Application.Current.Dispatcher.Invoke(() => {
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
this._pictureGalleryService.IncrementNewPhotoCount();
|
{
|
||||||
this._pictureGalleryService.LoadThumbnailsToCache();
|
_pictureGalleryService.IncrementNewPhotoCount();
|
||||||
|
_pictureGalleryService.LoadThumbnailsToCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Füge neues Foto zur PhotoPrism Upload-Queue hinzu (wenn Auto-Upload aktiviert)
|
_photoPrismUploadQueueService.QueueNewPhoto(savedPath);
|
||||||
this._photoPrismUploadQueueService.QueueNewPhoto(savedPhotoPath);
|
|
||||||
this._logger.Info($"Foto zur PhotoPrism Upload-Queue hinzugefügt: {Info.FileName}");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.ReportError(ex.Message);
|
_logger.Error(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void ErrorHandler_NonSevereErrorHappened(object sender, ErrorCode ex)
|
private void ErrorHandler_NonSevereErrorHappened(object sender, ErrorCode ex) =>
|
||||||
{
|
_logger.Error($"SDK Error: {ex} (0x{(int)ex:X})");
|
||||||
this.ReportError($"SDK Error code: {ex} ({((int)ex).ToString("X")})");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void ErrorHandler_SevereErrorHappened(object sender, Exception ex)
|
private void ErrorHandler_SevereErrorHappened(object sender, Exception ex) =>
|
||||||
{
|
_logger.Error(ex.Message);
|
||||||
this.ReportError(ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
using CamBooth.App.Core.AppSettings;
|
|
||||||
using CamBooth.App.Core.Logging;
|
using CamBooth.App.Core.Logging;
|
||||||
using CamBooth.App.Features.Camera;
|
using CamBooth.App.Features.Camera;
|
||||||
|
|
||||||
@ -12,87 +11,69 @@ using EOSDigital.API;
|
|||||||
|
|
||||||
namespace CamBooth.App.Features.LiveView;
|
namespace CamBooth.App.Features.LiveView;
|
||||||
|
|
||||||
public partial class LiveViewPage : Page
|
public partial class LiveViewPage : Page, IDisposable
|
||||||
{
|
{
|
||||||
private readonly AppSettingsService _appSettings;
|
|
||||||
|
|
||||||
private readonly CameraService _cameraService;
|
private readonly CameraService _cameraService;
|
||||||
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
private readonly ImageBrush _bgBrush = new();
|
||||||
private readonly ImageBrush bgbrush = new();
|
|
||||||
|
|
||||||
private readonly Action<BitmapImage> SetImageAction;
|
|
||||||
|
|
||||||
|
|
||||||
public LiveViewPage(Logger logger, AppSettingsService appSettings, CameraService cameraService)
|
public LiveViewPage(Logger logger, CameraService cameraService)
|
||||||
{
|
{
|
||||||
this._logger = logger;
|
_logger = logger;
|
||||||
this._appSettings = appSettings;
|
_cameraService = cameraService;
|
||||||
this._cameraService = cameraService;
|
|
||||||
this.InitializeComponent();
|
|
||||||
this.SetImageAction = img => { this.bgbrush.ImageSource = img; };
|
|
||||||
|
|
||||||
// Configure the image brush
|
InitializeComponent();
|
||||||
this.bgbrush.Stretch = Stretch.UniformToFill;
|
|
||||||
this.bgbrush.AlignmentX = AlignmentX.Center;
|
|
||||||
this.bgbrush.AlignmentY = AlignmentY.Center;
|
|
||||||
|
|
||||||
this.LVCanvas.Background = this.bgbrush;
|
_bgBrush.Stretch = Stretch.UniformToFill;
|
||||||
|
_bgBrush.AlignmentX = AlignmentX.Center;
|
||||||
|
_bgBrush.AlignmentY = AlignmentY.Center;
|
||||||
|
LVCanvas.Background = _bgBrush;
|
||||||
|
|
||||||
// Apply horizontal flip on the Canvas using RenderTransform
|
var transformGroup = new TransformGroup();
|
||||||
TransformGroup transformGroup = new();
|
|
||||||
transformGroup.Children.Add(new ScaleTransform { ScaleX = -1, ScaleY = 1 });
|
transformGroup.Children.Add(new ScaleTransform { ScaleX = -1, ScaleY = 1 });
|
||||||
transformGroup.Children.Add(new TranslateTransform { X = 1, Y = 0 });
|
transformGroup.Children.Add(new TranslateTransform { X = 1, Y = 0 });
|
||||||
this.LVCanvas.RenderTransform = transformGroup;
|
LVCanvas.RenderTransform = transformGroup;
|
||||||
this.LVCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
|
LVCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
cameraService.ConnectCamera();
|
cameraService.ConnectCamera();
|
||||||
|
cameraService.LiveViewUpdated += OnLiveViewUpdated;
|
||||||
// Verify that camera session is open before subscribing to events
|
_logger.Info("LiveViewPage initialized successfully");
|
||||||
if (cameraService._mainCamera != null && cameraService._mainCamera.SessionOpen)
|
|
||||||
{
|
|
||||||
cameraService._mainCamera.LiveViewUpdated += this.MainCamera_OnLiveViewUpdated;
|
|
||||||
this._logger.Info("LiveViewPage initialized successfully");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this._logger.Error("Camera session is not open after connection attempt");
|
|
||||||
throw new InvalidOperationException("Camera session failed to open");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this._logger.Error($"Failed to initialize LiveViewPage: {ex.Message}");
|
_logger.Error($"Failed to initialize LiveViewPage: {ex.Message}");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void MainCamera_OnLiveViewUpdated(ICamera sender, Stream img)
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cameraService.LiveViewUpdated -= OnLiveViewUpdated;
|
||||||
|
_cameraService.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnLiveViewUpdated(Stream img)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using WrapStream s = new(img);
|
using WrapStream s = new(img);
|
||||||
img.Position = 0;
|
img.Position = 0;
|
||||||
BitmapImage EvfImage = new();
|
var evfImage = new BitmapImage();
|
||||||
EvfImage.BeginInit();
|
evfImage.BeginInit();
|
||||||
EvfImage.StreamSource = s;
|
evfImage.StreamSource = s;
|
||||||
EvfImage.CacheOption = BitmapCacheOption.OnLoad;
|
evfImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
EvfImage.EndInit();
|
evfImage.EndInit();
|
||||||
EvfImage.Freeze();
|
evfImage.Freeze();
|
||||||
Application.Current.Dispatcher.BeginInvoke(this.SetImageAction, EvfImage);
|
Application.Current.Dispatcher.BeginInvoke(() => _bgBrush.ImageSource = evfImage);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this._logger.Error(ex.Message);
|
_logger.Error(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this._cameraService.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,14 +1,11 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
|
||||||
using CamBooth.App.Core.AppSettings;
|
using CamBooth.App.Core.AppSettings;
|
||||||
using CamBooth.App.Core.Logging;
|
using CamBooth.App.Core.Logging;
|
||||||
using CamBooth.App.Features.Camera;
|
using CamBooth.App.Features.Camera;
|
||||||
@ -16,7 +13,6 @@ using CamBooth.App.Features.DebugConsole;
|
|||||||
using CamBooth.App.Features.LiveView;
|
using CamBooth.App.Features.LiveView;
|
||||||
using CamBooth.App.Features.PhotoPrismUpload;
|
using CamBooth.App.Features.PhotoPrismUpload;
|
||||||
using CamBooth.App.Features.PictureGallery;
|
using CamBooth.App.Features.PictureGallery;
|
||||||
|
|
||||||
using Wpf.Ui.Controls;
|
using Wpf.Ui.Controls;
|
||||||
|
|
||||||
namespace CamBooth.App;
|
namespace CamBooth.App;
|
||||||
@ -27,37 +23,23 @@ namespace CamBooth.App;
|
|||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
private readonly AppSettingsService _appSettings;
|
private readonly AppSettingsService _appSettings;
|
||||||
|
|
||||||
private readonly PictureGalleryService _pictureGalleryService;
|
private readonly PictureGalleryService _pictureGalleryService;
|
||||||
|
|
||||||
private readonly CameraService _cameraService;
|
private readonly CameraService _cameraService;
|
||||||
|
|
||||||
private readonly PhotoPrismUploadService _photoPrismUploadService;
|
private readonly PhotoPrismUploadService _photoPrismUploadService;
|
||||||
|
private readonly MainWindowViewModel _viewModel;
|
||||||
private bool _isDebugConsoleVisible = true;
|
|
||||||
|
|
||||||
private bool _isPicturePanelVisible = false;
|
|
||||||
|
|
||||||
private LiveViewPage? _liveViewPage;
|
private LiveViewPage? _liveViewPage;
|
||||||
|
|
||||||
private bool _isPhotoProcessRunning;
|
|
||||||
|
|
||||||
private bool _isCameraStarted;
|
private bool _isCameraStarted;
|
||||||
|
private bool _isPicturePanelVisible;
|
||||||
|
private bool _isDebugConsoleVisible;
|
||||||
private bool _isShutdownSliderOpen;
|
private bool _isShutdownSliderOpen;
|
||||||
|
|
||||||
private const string ShutdownGlyphClosed = "\uE7E8";
|
private const string ShutdownGlyphClosed = "\uE7E8";
|
||||||
|
|
||||||
private const string ShutdownGlyphOpen = "\uE711";
|
private const string ShutdownGlyphOpen = "\uE711";
|
||||||
|
|
||||||
private const double ShutdownSliderOffset = 160;
|
private const double ShutdownSliderOffset = 160;
|
||||||
|
|
||||||
private readonly DispatcherTimer _focusStatusAnimationTimer = new() { Interval = TimeSpan.FromMilliseconds(250) };
|
private readonly DispatcherTimer _focusStatusAnimationTimer = new() { Interval = TimeSpan.FromMilliseconds(250) };
|
||||||
|
|
||||||
private readonly DispatcherTimer _galleryPromptTimer = new() { Interval = TimeSpan.FromSeconds(5) };
|
|
||||||
|
|
||||||
private int _focusStatusDots;
|
private int _focusStatusDots;
|
||||||
|
|
||||||
|
|
||||||
@ -66,545 +48,207 @@ public partial class MainWindow : Window
|
|||||||
AppSettingsService appSettings,
|
AppSettingsService appSettings,
|
||||||
PictureGalleryService pictureGalleryService,
|
PictureGalleryService pictureGalleryService,
|
||||||
CameraService cameraService,
|
CameraService cameraService,
|
||||||
PhotoPrismUploadService photoPrismUploadService)
|
PhotoPrismUploadService photoPrismUploadService,
|
||||||
|
MainWindowViewModel viewModel)
|
||||||
{
|
{
|
||||||
this._logger = logger;
|
_logger = logger;
|
||||||
this._appSettings = appSettings;
|
_appSettings = appSettings;
|
||||||
this._pictureGalleryService = pictureGalleryService;
|
_pictureGalleryService = pictureGalleryService;
|
||||||
this._cameraService = cameraService;
|
_cameraService = cameraService;
|
||||||
this._photoPrismUploadService = photoPrismUploadService;
|
_photoPrismUploadService = photoPrismUploadService;
|
||||||
|
_viewModel = viewModel;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
this.SetVisibilityDebugConsole(_appSettings.IsDebugConsoleVisible);
|
|
||||||
this.SetVisibilityPicturePanel(this._isPicturePanelVisible);
|
|
||||||
|
|
||||||
// Lade Thumbnails asynchron und zeige dann den Welcome Screen
|
// Wire ViewModel events to UI
|
||||||
_ = InitializeAsync();
|
_viewModel.LoadingProgressChanged += (status, count) =>
|
||||||
|
|
||||||
this.Closing += OnClosing;
|
|
||||||
TimerControlRectangleAnimation.OnTimerEllapsed += TimerControlRectangleAnimation_OnTimerEllapsed;
|
|
||||||
this._focusStatusAnimationTimer.Tick += (_, _) =>
|
|
||||||
{
|
{
|
||||||
this._focusStatusDots = (this._focusStatusDots + 1) % 4;
|
LoadingStatusText.Text = status;
|
||||||
this.CaptureStatusText.Text = $"Scharfstellen{new string('.', this._focusStatusDots)}";
|
LoadingCountText.Text = count;
|
||||||
};
|
};
|
||||||
this._galleryPromptTimer.Tick += (_, _) => this.HideGalleryPrompt();
|
_viewModel.InitializationCompleted += () =>
|
||||||
|
{
|
||||||
|
LoadingOverlay.Visibility = Visibility.Collapsed;
|
||||||
|
WelcomeOverlay.Visibility = Visibility.Visible;
|
||||||
|
};
|
||||||
|
_viewModel.GalleryPromptRequested += () => GalleryPrompt.Visibility = Visibility.Visible;
|
||||||
|
_viewModel.GalleryPromptDismissed += () => GalleryPrompt.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
// Subscribe to new photo count changes
|
// Wire service events
|
||||||
this._pictureGalleryService.NewPhotoCountChanged += PictureGalleryService_NewPhotoCountChanged;
|
_pictureGalleryService.NewPhotoCountChanged += OnNewPhotoCountChanged;
|
||||||
|
TimerControlRectangleAnimation.OnTimerEllapsed += OnTimerElapsed;
|
||||||
|
|
||||||
this.DebugCloseButton.Visibility = Visibility.Collapsed;
|
// Focus animation timer
|
||||||
this.HideDebugButton.Visibility = this._appSettings.IsDebugConsoleVisible ? Visibility.Visible : Visibility.Collapsed;
|
_focusStatusAnimationTimer.Tick += (_, _) =>
|
||||||
|
{
|
||||||
|
_focusStatusDots = (_focusStatusDots + 1) % 4;
|
||||||
|
CaptureStatusText.Text = $"Scharfstellen{new string('.', _focusStatusDots)}";
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize PhotoPrism upload if auto-upload is enabled
|
// Initial UI state
|
||||||
if (appSettings.PhotoPrismAutoUploadEnabled)
|
_isDebugConsoleVisible = _appSettings.IsDebugConsoleVisible;
|
||||||
{
|
SetVisibilityDebugConsole(_isDebugConsoleVisible);
|
||||||
logger.Info("PhotoPrism Auto-Upload ist aktiviert. Authentifiziere...");
|
SetVisibilityPicturePanel(false);
|
||||||
_ = Task.Run(async () =>
|
DebugCloseButton.Visibility = Visibility.Collapsed;
|
||||||
{
|
HideDebugButton.Visibility = _appSettings.IsDebugConsoleVisible ? Visibility.Visible : Visibility.Collapsed;
|
||||||
var authSuccess = await _photoPrismUploadService.AuthenticateAsync();
|
|
||||||
if (authSuccess)
|
Closing += OnClosing;
|
||||||
{
|
|
||||||
logger.Info("PhotoPrism-Authentifizierung erfolgreich!");
|
_ = _viewModel.InitializeAsync();
|
||||||
}
|
_logger.Info($"config file loaded: '{appSettings.ConfigFileName}'");
|
||||||
else
|
_logger.Info("MainWindow initialized");
|
||||||
{
|
|
||||||
logger.Warning("PhotoPrism-Authentifizierung fehlgeschlagen. Auto-Upload wird nicht funktionieren.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info($"config file loaded: '{appSettings.ConfigFileName}'");
|
|
||||||
logger.Info("MainWindow initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InitializeAsync()
|
#region Event handlers (wired in XAML or in ctor)
|
||||||
|
|
||||||
|
private void OnTimerElapsed()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Zeige Ladeanzeige
|
_viewModel.OnTimerElapsed();
|
||||||
this.LoadingOverlay.Visibility = Visibility.Visible;
|
|
||||||
this.WelcomeOverlay.Visibility = Visibility.Collapsed;
|
|
||||||
|
|
||||||
// Lade Thumbnails mit Progress-Updates
|
|
||||||
await LoadThumbnailsWithProgress();
|
|
||||||
|
|
||||||
// Warte kurz, damit der Benutzer die Fertigstellung sehen kann
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
// Verstecke Ladeanzeige und zeige Welcome Screen
|
|
||||||
this.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
this.LoadingOverlay.Visibility = Visibility.Collapsed;
|
|
||||||
this.WelcomeOverlay.Visibility = Visibility.Visible;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._logger.Info("Initialization completed successfully");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this._logger.Error($"Initialization error: {ex.Message}");
|
_logger.Error(ex.Message);
|
||||||
// Bei Fehler trotzdem zum Welcome Screen wechseln
|
|
||||||
this.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
this.LoadingOverlay.Visibility = Visibility.Collapsed;
|
|
||||||
this.WelcomeOverlay.Visibility = Visibility.Visible;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadThumbnailsWithProgress()
|
|
||||||
{
|
|
||||||
string pictureLocation = this._appSettings.PictureLocation;
|
|
||||||
|
|
||||||
// Sicherstellen, dass das Verzeichnis existiert
|
|
||||||
if (!Directory.Exists(pictureLocation))
|
|
||||||
{
|
|
||||||
this._logger.Info($"Picture directory does not exist: '{pictureLocation}'. Creating it...");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(pictureLocation);
|
|
||||||
this._logger.Info($"Picture directory created: '{pictureLocation}'");
|
|
||||||
|
|
||||||
this.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
this.LoadingStatusText.Text = "Keine Fotos gefunden";
|
|
||||||
this.LoadingCountText.Text = "0 Fotos";
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this._logger.Error($"Failed to create picture directory: {ex.Message}");
|
|
||||||
this.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
this.LoadingStatusText.Text = "Fehler beim Erstellen des Foto-Ordners";
|
|
||||||
this.LoadingCountText.Text = "";
|
|
||||||
});
|
|
||||||
await Task.Delay(2000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zähle Bilddateien
|
|
||||||
string[] imageExtensions = { ".jpg", ".jpeg", ".png", ".bmp", ".gif" };
|
|
||||||
var picturePaths = Directory.EnumerateFiles(pictureLocation)
|
|
||||||
.Where(f => imageExtensions.Contains(Path.GetExtension(f).ToLower()))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
int totalCount = picturePaths.Count;
|
|
||||||
|
|
||||||
if (totalCount == 0)
|
|
||||||
{
|
|
||||||
this._logger.Info($"No pictures found in directory: '{pictureLocation}'");
|
|
||||||
this.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
this.LoadingStatusText.Text = "Keine Fotos gefunden";
|
|
||||||
this.LoadingCountText.Text = "Bereit für neue Aufnahmen!";
|
|
||||||
});
|
|
||||||
await Task.Delay(1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update UI mit Gesamtanzahl
|
|
||||||
this.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
this.LoadingStatusText.Text = $"Lade {totalCount} Foto{(totalCount != 1 ? "s" : "")}...";
|
|
||||||
this.LoadingCountText.Text = $"0 / {totalCount}";
|
|
||||||
});
|
|
||||||
|
|
||||||
// Lade Thumbnails
|
|
||||||
await this._pictureGalleryService.LoadThumbnailsToCache();
|
|
||||||
|
|
||||||
// Update UI nach dem Laden
|
|
||||||
this.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
this.LoadingStatusText.Text = "Fotos erfolgreich geladen!";
|
|
||||||
this.LoadingCountText.Text = $"{totalCount} Foto{(totalCount != 1 ? "s" : "")} bereit";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void TimerControlRectangleAnimation_OnTimerEllapsed()
|
|
||||||
{
|
|
||||||
var photoTakenSuccessfully = false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this._cameraService.TakePhoto();
|
|
||||||
photoTakenSuccessfully = true;
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
//TODO: mit content dialog ersetzen
|
|
||||||
System.Windows.MessageBox.Show("Sorry, da ging was schief! Bitte nochmal probieren.");
|
System.Windows.MessageBox.Show("Sorry, da ging was schief! Bitte nochmal probieren.");
|
||||||
this._logger.Info(exception.Message);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
this.StopFocusStatusAnimation();
|
StopFocusStatusAnimation();
|
||||||
this.CaptureStatusText.Visibility = Visibility.Collapsed;
|
CaptureStatusText.Visibility = Visibility.Collapsed;
|
||||||
SwitchButtonAndTimerPanel();
|
SwitchButtonAndTimerPanel();
|
||||||
this._isPhotoProcessRunning = false;
|
}
|
||||||
if (photoTakenSuccessfully)
|
}
|
||||||
|
|
||||||
|
private void OnNewPhotoCountChanged(object? sender, int count)
|
||||||
{
|
{
|
||||||
this.ShowGalleryPrompt();
|
NewPhotosBadge.Visibility = count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
if (count > 0)
|
||||||
|
NewPhotoCountText.Text = count.ToString();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void SetVisibilityPicturePanel(bool visibility)
|
|
||||||
{
|
|
||||||
if (visibility)
|
|
||||||
{
|
|
||||||
this.HideGalleryPrompt();
|
|
||||||
this.PicturePanel.Navigate(new PictureGalleryPage(this._appSettings, this._logger, this._pictureGalleryService, this._photoPrismUploadService));
|
|
||||||
// Reset new photo count when opening gallery
|
|
||||||
this._pictureGalleryService.ResetNewPhotoCount();
|
|
||||||
// Blende unnötige Buttons aus, wenn Galerie geöffnet wird
|
|
||||||
this.ButtonPanel.Visibility = Visibility.Hidden;
|
|
||||||
this.ActionButtonsContainer.Visibility = Visibility.Hidden;
|
|
||||||
this.ShutdownDock.Visibility = Visibility.Hidden;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.PicturePanel.ClearValue(MainWindow.ContentProperty);
|
|
||||||
// Stelle Buttons wieder her, wenn Galerie geschlossen wird
|
|
||||||
this.ButtonPanel.Visibility = Visibility.Visible;
|
|
||||||
this.ActionButtonsContainer.Visibility = Visibility.Visible;
|
|
||||||
this.ShutdownDock.Visibility = Visibility.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._isPicturePanelVisible = !visibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void ClosePicturePanel()
|
|
||||||
{
|
|
||||||
if (this.PicturePanel.Content is PictureGalleryPage pictureGalleryPage)
|
|
||||||
{
|
|
||||||
pictureGalleryPage.CloseOpenDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.PicturePanel.Content is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.SetVisibilityPicturePanel(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void OnClosing(object? sender, CancelEventArgs e)
|
private void OnClosing(object? sender, CancelEventArgs e)
|
||||||
{
|
{
|
||||||
this.CloseCameraSessionSafely();
|
|
||||||
this._liveViewPage?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void StartExperience(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
this.StartLiveViewIfNeeded();
|
|
||||||
this.WelcomeOverlay.Visibility = Visibility.Collapsed;
|
|
||||||
this.ButtonPanel.Visibility = Visibility.Visible;
|
|
||||||
this.ActionButtonsContainer.Visibility = Visibility.Visible;
|
|
||||||
this.ShutdownDock.Visibility = Visibility.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartLiveViewIfNeeded()
|
|
||||||
{
|
|
||||||
if (this._isCameraStarted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this._liveViewPage = new LiveViewPage(this._logger, this._appSettings, this._cameraService);
|
_cameraService.CloseSession();
|
||||||
this.MainFrame.Navigate(this._liveViewPage);
|
|
||||||
this._isCameraStarted = true;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this._logger.Error($"Failed to start live view: {ex.Message}\n{ex.StackTrace}");
|
_logger.Error($"Fehler beim Schließen der Kamera-Session: {ex.Message}");
|
||||||
System.Windows.MessageBox.Show($"Failed to initialize camera. Please ensure the camera is properly connected.\n\nError: {ex.Message}",
|
|
||||||
"Camera Connection Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_liveViewPage?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
private void SetVisibilityDebugConsole(object sender, RoutedEventArgs e)
|
// XAML-bound handlers
|
||||||
|
private void StartExperience(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
this.SetVisibilityDebugConsole(this._isDebugConsoleVisible);
|
StartLiveViewIfNeeded();
|
||||||
|
WelcomeOverlay.Visibility = Visibility.Collapsed;
|
||||||
|
ButtonPanel.Visibility = Visibility.Visible;
|
||||||
|
ActionButtonsContainer.Visibility = Visibility.Visible;
|
||||||
|
ShutdownDock.Visibility = Visibility.Visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void SetVisibilityDebugConsole(bool visibility)
|
|
||||||
{
|
|
||||||
if (!_appSettings.IsDebugConsoleVisible)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visibility)
|
|
||||||
{
|
|
||||||
this.DebugFrame.Navigate(new DebugConsolePage(this._logger));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.DebugFrame.ClearValue(MainWindow.ContentProperty);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._isDebugConsoleVisible = !visibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private async void StartTakePhotoProcess(object sender, RoutedEventArgs e)
|
private async void StartTakePhotoProcess(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
this.HideGalleryPrompt();
|
if (_viewModel.IsPhotoProcessRunning) return;
|
||||||
this.ClosePicturePanel();
|
|
||||||
|
|
||||||
if (this._isPhotoProcessRunning)
|
ClosePicturePanel();
|
||||||
{
|
_viewModel.StartPhotoProcess();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._isPhotoProcessRunning = true;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SwitchButtonAndTimerPanel();
|
SwitchButtonAndTimerPanel();
|
||||||
|
TimerControlRectangleAnimation.StartTimer(_appSettings.PhotoCountdownSeconds);
|
||||||
|
StartFocusStatusAnimation();
|
||||||
|
CaptureStatusText.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
TimerControlRectangleAnimation.StartTimer(this._appSettings.PhotoCountdownSeconds);
|
await Task.Delay(TimeSpan.FromSeconds(_appSettings.FocusDelaySeconds));
|
||||||
this.StartFocusStatusAnimation();
|
if (!_viewModel.IsPhotoProcessRunning) return;
|
||||||
this.CaptureStatusText.Visibility = Visibility.Visible;
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(this._appSettings.FocusDelaySeconds));
|
|
||||||
if (!this._isPhotoProcessRunning)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this._cameraService.PrepareFocusAsync(focusTimeoutMs: this._appSettings.FocusTimeoutMs);
|
await _cameraService.PrepareFocusAsync(_appSettings.FocusTimeoutMs);
|
||||||
this.StopFocusStatusAnimation();
|
StopFocusStatusAnimation();
|
||||||
this.CaptureStatusText.Visibility = Visibility.Collapsed;
|
CaptureStatusText.Visibility = Visibility.Collapsed;
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
this._isPhotoProcessRunning = false;
|
|
||||||
this.StopFocusStatusAnimation();
|
|
||||||
this.CaptureStatusText.Visibility = Visibility.Collapsed;
|
|
||||||
if (this.TimerPanel.Visibility == Visibility.Visible)
|
|
||||||
{
|
|
||||||
SwitchButtonAndTimerPanel();
|
|
||||||
}
|
|
||||||
this._logger.Error(exception.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SwitchButtonAndTimerPanel()
|
|
||||||
{
|
|
||||||
this.ButtonPanel.Visibility = this.ButtonPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden;
|
|
||||||
this.ActionButtonsContainer.Visibility = this.ActionButtonsContainer.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden;
|
|
||||||
this.TimerPanel.Visibility = this.TimerPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetVisibilityPicturePanel(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
this.SetVisibilityPicturePanel(this._isPicturePanelVisible);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void CloseApp(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
this.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void ShutdownWindows(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
// Show confirmation dialog
|
|
||||||
var confirmDialog = new ContentDialog(this.DialogPresenter);
|
|
||||||
confirmDialog.Title = "Sicherheitsabfrage";
|
|
||||||
confirmDialog.Content = "Möchtest du die Fotobox wirklich ausschalten?";
|
|
||||||
confirmDialog.PrimaryButtonText = "Ja, ausschalten";
|
|
||||||
confirmDialog.CloseButtonText = "Abbrechen";
|
|
||||||
confirmDialog.DefaultButton = ContentDialogButton.Close;
|
|
||||||
confirmDialog.PrimaryButtonAppearance = ControlAppearance.Danger;
|
|
||||||
confirmDialog.CloseButtonAppearance = ControlAppearance.Secondary;
|
|
||||||
confirmDialog.Background = new SolidColorBrush(Colors.White);
|
|
||||||
confirmDialog.Foreground = new SolidColorBrush(Colors.Black);
|
|
||||||
|
|
||||||
var result = await confirmDialog.ShowAsync();
|
|
||||||
|
|
||||||
// Only proceed with shutdown if user confirmed
|
|
||||||
if (result != ContentDialogResult.Primary)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kamera-Session vor Abmeldung/Shutdown sauber schließen.
|
|
||||||
this.CloseCameraSessionSafely();
|
|
||||||
|
|
||||||
if (this._appSettings.IsShutdownEnabled)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Process.Start(new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "shutdown",
|
|
||||||
Arguments = "/s /t 0",
|
|
||||||
CreateNoWindow = true,
|
|
||||||
UseShellExecute = false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
this._logger.Error(exception.Message);
|
|
||||||
System.Windows.MessageBox.Show("Windows konnte nicht heruntergefahren werden. Bitte erneut versuchen.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Process.Start(new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "shutdown",
|
|
||||||
Arguments = "/l", // = logoff (abmelden)
|
|
||||||
CreateNoWindow = true,
|
|
||||||
UseShellExecute = false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
this._logger.Error(exception.Message);
|
|
||||||
System.Windows.MessageBox.Show("Abmeldung fehlgeschlagen");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CloseCameraSessionSafely()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this._cameraService.CloseSession();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this._logger.Error($"Fehler beim Schließen der Kamera-Session: {ex.Message}");
|
_viewModel.CancelPhotoProcess();
|
||||||
|
StopFocusStatusAnimation();
|
||||||
|
CaptureStatusText.Visibility = Visibility.Collapsed;
|
||||||
|
if (TimerPanel.Visibility == Visibility.Visible)
|
||||||
|
SwitchButtonAndTimerPanel();
|
||||||
|
_logger.Error(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetVisibilityPicturePanel(object sender, MouseButtonEventArgs e) =>
|
||||||
|
SetVisibilityPicturePanel(!_isPicturePanelVisible);
|
||||||
|
|
||||||
|
private void SetVisibilityDebugConsole(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_isDebugConsoleVisible = !_isDebugConsoleVisible;
|
||||||
|
SetVisibilityDebugConsole(_isDebugConsoleVisible);
|
||||||
|
}
|
||||||
|
|
||||||
private void ToggleShutdownSlider(object sender, RoutedEventArgs e)
|
private void ToggleShutdownSlider(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
this._isShutdownSliderOpen = !this._isShutdownSliderOpen;
|
_isShutdownSliderOpen = !_isShutdownSliderOpen;
|
||||||
this.ShutdownToggleButton.Content = this._isShutdownSliderOpen ? ShutdownGlyphOpen : ShutdownGlyphClosed;
|
ShutdownToggleButton.Content = _isShutdownSliderOpen ? ShutdownGlyphOpen : ShutdownGlyphClosed;
|
||||||
this.AnimateShutdownSlider(this._isShutdownSliderOpen);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AnimateShutdownSlider(bool open)
|
|
||||||
{
|
|
||||||
var animation = new DoubleAnimation
|
var animation = new DoubleAnimation
|
||||||
{
|
{
|
||||||
To = open ? 0 : ShutdownSliderOffset,
|
To = _isShutdownSliderOpen ? 0 : ShutdownSliderOffset,
|
||||||
Duration = TimeSpan.FromMilliseconds(250),
|
Duration = TimeSpan.FromMilliseconds(250),
|
||||||
EasingFunction = new QuadraticEase()
|
EasingFunction = new QuadraticEase()
|
||||||
};
|
};
|
||||||
|
ShutdownSliderTransform.BeginAnimation(System.Windows.Media.TranslateTransform.XProperty, animation);
|
||||||
this.ShutdownSliderTransform.BeginAnimation(System.Windows.Media.TranslateTransform.XProperty, animation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartFocusStatusAnimation()
|
private async void ShutdownWindows(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
this._focusStatusDots = 0;
|
var confirmDialog = new ContentDialog(DialogPresenter)
|
||||||
this.CaptureStatusText.Text = "Scharfstellen";
|
|
||||||
this._focusStatusAnimationTimer.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopFocusStatusAnimation()
|
|
||||||
{
|
{
|
||||||
this._focusStatusAnimationTimer.Stop();
|
Title = "Sicherheitsabfrage",
|
||||||
this._focusStatusDots = 0;
|
Content = "Möchtest du die Fotobox wirklich ausschalten?",
|
||||||
}
|
PrimaryButtonText = "Ja, ausschalten",
|
||||||
|
CloseButtonText = "Abbrechen",
|
||||||
|
DefaultButton = ContentDialogButton.Close,
|
||||||
|
PrimaryButtonAppearance = ControlAppearance.Danger,
|
||||||
|
CloseButtonAppearance = ControlAppearance.Secondary,
|
||||||
|
Background = new SolidColorBrush(Colors.White),
|
||||||
|
Foreground = new SolidColorBrush(Colors.Black)
|
||||||
|
};
|
||||||
|
|
||||||
private void ShowGalleryPrompt()
|
if (await confirmDialog.ShowAsync() != ContentDialogResult.Primary) return;
|
||||||
{
|
|
||||||
this.GalleryPrompt.Visibility = Visibility.Visible;
|
|
||||||
this._galleryPromptTimer.Stop();
|
|
||||||
this._galleryPromptTimer.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HideGalleryPrompt()
|
|
||||||
{
|
|
||||||
this._galleryPromptTimer.Stop();
|
|
||||||
this.GalleryPrompt.Visibility = Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OpenGalleryFromPrompt(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
this.HideGalleryPrompt();
|
|
||||||
this.SetVisibilityPicturePanel(true);
|
|
||||||
|
|
||||||
// Wait a bit for the page to load before showing the dialog
|
|
||||||
await Task.Delay(300);
|
|
||||||
|
|
||||||
// Show the latest photo in a dialog
|
|
||||||
await this.ShowLatestPhotoDialogAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ShowLatestPhotoDialogAsync()
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get the latest photo path
|
_cameraService.CloseSession();
|
||||||
await this._pictureGalleryService.LoadThumbnailsToCache(1);
|
}
|
||||||
var latestPhotos = this._pictureGalleryService.ThumbnailsOrderedByNewestDescending;
|
catch
|
||||||
|
|
||||||
if (latestPhotos.Count == 0)
|
|
||||||
{
|
{
|
||||||
this._logger.Error("No photos found in gallery");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the file path from the BitmapImage source
|
var (args, errorMsg) = _appSettings.IsShutdownEnabled
|
||||||
var latestPhoto = latestPhotos[0];
|
? ("/s /t 0", "Windows konnte nicht heruntergefahren werden.")
|
||||||
if (latestPhoto.UriSource == null)
|
: ("/l", "Abmeldung fehlgeschlagen.");
|
||||||
{
|
|
||||||
this._logger.Error("Latest photo UriSource is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string photoPath = Uri.UnescapeDataString(latestPhoto.UriSource.AbsolutePath);
|
try
|
||||||
this._logger.Info($"Opening photo dialog for: {photoPath}");
|
|
||||||
|
|
||||||
// Get the current gallery page
|
|
||||||
if (this.PicturePanel.Content is PictureGalleryPage galleryPage)
|
|
||||||
{
|
{
|
||||||
// Show the photo dialog
|
Process.Start(new ProcessStartInfo
|
||||||
await galleryPage.ShowPhotoDialogAsync(photoPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
this._logger.Error("PicturePanel content is not a PictureGalleryPage");
|
FileName = "shutdown",
|
||||||
}
|
Arguments = args,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this._logger.Error($"Error showing photo dialog: {ex.Message}");
|
_logger.Error(ex.Message);
|
||||||
}
|
System.Windows.MessageBox.Show(errorMsg);
|
||||||
}
|
|
||||||
|
|
||||||
private void PictureGalleryService_NewPhotoCountChanged(object? sender, int newPhotoCount)
|
|
||||||
{
|
|
||||||
if (newPhotoCount > 0)
|
|
||||||
{
|
|
||||||
this.NewPhotosBadge.Visibility = Visibility.Visible;
|
|
||||||
this.NewPhotoCountText.Text = newPhotoCount.ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.NewPhotosBadge.Visibility = Visibility.Collapsed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -612,28 +256,151 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this._logger.Info("Zeige PhotoPrism Album-QR-Code an...");
|
var qrCodeImage = _photoPrismUploadService.GenerateAlbumQRCode();
|
||||||
|
|
||||||
// Generiere QR-Code
|
|
||||||
var qrCodeImage = this._photoPrismUploadService.GenerateAlbumQRCode();
|
|
||||||
|
|
||||||
if (qrCodeImage == null)
|
if (qrCodeImage == null)
|
||||||
{
|
{
|
||||||
System.Windows.MessageBox.Show("QR-Code konnte nicht generiert werden. Bitte überprüfe die PhotoPrism-Konfiguration.",
|
System.Windows.MessageBox.Show(
|
||||||
|
"QR-Code konnte nicht generiert werden. Bitte überprüfe die PhotoPrism-Konfiguration.",
|
||||||
"Fehler", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
"Fehler", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Öffne QR-Code-Fenster
|
|
||||||
var qrWindow = new PhotoPrismQRCodeDisplayWindow();
|
var qrWindow = new PhotoPrismQRCodeDisplayWindow();
|
||||||
qrWindow.SetQRCode(qrCodeImage);
|
qrWindow.SetQRCode(qrCodeImage);
|
||||||
qrWindow.ShowDialog();
|
qrWindow.ShowDialog();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this._logger.Error($"Fehler beim Anzeigen des PhotoPrism QR-Codes: {ex.Message}");
|
_logger.Error($"Fehler beim Anzeigen des QR-Codes: {ex.Message}");
|
||||||
System.Windows.MessageBox.Show($"Fehler beim Anzeigen des QR-Codes: {ex.Message}",
|
System.Windows.MessageBox.Show(
|
||||||
|
$"Fehler beim Anzeigen des QR-Codes: {ex.Message}",
|
||||||
"Fehler", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
"Fehler", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void OpenGalleryFromPrompt(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_viewModel.DismissGalleryPrompt();
|
||||||
|
SetVisibilityPicturePanel(true);
|
||||||
|
await Task.Delay(300);
|
||||||
|
await ShowLatestPhotoDialogAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseApp(object sender, RoutedEventArgs e) => Close();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region UI helpers
|
||||||
|
|
||||||
|
private void StartLiveViewIfNeeded()
|
||||||
|
{
|
||||||
|
if (_isCameraStarted) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_liveViewPage = new LiveViewPage(_logger, _cameraService);
|
||||||
|
MainFrame.Navigate(_liveViewPage);
|
||||||
|
_isCameraStarted = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error($"Failed to start live view: {ex.Message}\n{ex.StackTrace}");
|
||||||
|
System.Windows.MessageBox.Show(
|
||||||
|
$"Failed to initialize camera. Please ensure the camera is properly connected.\n\nError: {ex.Message}",
|
||||||
|
"Camera Connection Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClosePicturePanel()
|
||||||
|
{
|
||||||
|
if (PicturePanel.Content is PictureGalleryPage page)
|
||||||
|
page.CloseOpenDialog();
|
||||||
|
if (PicturePanel.Content is null) return;
|
||||||
|
SetVisibilityPicturePanel(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetVisibilityPicturePanel(bool visible)
|
||||||
|
{
|
||||||
|
if (visible)
|
||||||
|
{
|
||||||
|
_viewModel.DismissGalleryPrompt();
|
||||||
|
PicturePanel.Navigate(new PictureGalleryPage(_appSettings, _logger, _pictureGalleryService,
|
||||||
|
_photoPrismUploadService));
|
||||||
|
_pictureGalleryService.ResetNewPhotoCount();
|
||||||
|
ButtonPanel.Visibility = Visibility.Hidden;
|
||||||
|
ActionButtonsContainer.Visibility = Visibility.Hidden;
|
||||||
|
ShutdownDock.Visibility = Visibility.Hidden;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PicturePanel.ClearValue(ContentProperty);
|
||||||
|
ButtonPanel.Visibility = Visibility.Visible;
|
||||||
|
ActionButtonsContainer.Visibility = Visibility.Visible;
|
||||||
|
ShutdownDock.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isPicturePanelVisible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetVisibilityDebugConsole(bool visible)
|
||||||
|
{
|
||||||
|
if (!_appSettings.IsDebugConsoleVisible) return;
|
||||||
|
if (visible)
|
||||||
|
DebugFrame.Navigate(new DebugConsolePage(_logger));
|
||||||
|
else
|
||||||
|
DebugFrame.ClearValue(ContentProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SwitchButtonAndTimerPanel()
|
||||||
|
{
|
||||||
|
ButtonPanel.Visibility = ButtonPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden;
|
||||||
|
ActionButtonsContainer.Visibility = ActionButtonsContainer.Visibility == Visibility.Hidden
|
||||||
|
? Visibility.Visible
|
||||||
|
: Visibility.Hidden;
|
||||||
|
TimerPanel.Visibility = TimerPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartFocusStatusAnimation()
|
||||||
|
{
|
||||||
|
_focusStatusDots = 0;
|
||||||
|
CaptureStatusText.Text = "Scharfstellen";
|
||||||
|
_focusStatusAnimationTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopFocusStatusAnimation()
|
||||||
|
{
|
||||||
|
_focusStatusAnimationTimer.Stop();
|
||||||
|
_focusStatusDots = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowLatestPhotoDialogAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _pictureGalleryService.LoadThumbnailsToCache(1);
|
||||||
|
var latestPhotos = _pictureGalleryService.ThumbnailsOrderedByNewestDescending;
|
||||||
|
if (latestPhotos.Count == 0)
|
||||||
|
{
|
||||||
|
_logger.Error("No photos found in gallery");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var latestPhoto = latestPhotos[0];
|
||||||
|
if (latestPhoto.UriSource == null)
|
||||||
|
{
|
||||||
|
_logger.Error("Latest photo UriSource is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var photoPath = Uri.UnescapeDataString(latestPhoto.UriSource.AbsolutePath);
|
||||||
|
if (PicturePanel.Content is PictureGalleryPage galleryPage)
|
||||||
|
await galleryPage.ShowPhotoDialogAsync(photoPath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error($"Error showing photo dialog: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
148
src/CamBooth/CamBooth.App/MainWindowViewModel.cs
Normal file
148
src/CamBooth/CamBooth.App/MainWindowViewModel.cs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
using CamBooth.App.Core.AppSettings;
|
||||||
|
using CamBooth.App.Core.Logging;
|
||||||
|
using CamBooth.App.Features.Camera;
|
||||||
|
using CamBooth.App.Features.PhotoPrismUpload;
|
||||||
|
using CamBooth.App.Features.PictureGallery;
|
||||||
|
|
||||||
|
namespace CamBooth.App;
|
||||||
|
|
||||||
|
public class MainWindowViewModel
|
||||||
|
{
|
||||||
|
private readonly Logger _logger;
|
||||||
|
private readonly AppSettingsService _appSettings;
|
||||||
|
private readonly PictureGalleryService _pictureGalleryService;
|
||||||
|
private readonly CameraService _cameraService;
|
||||||
|
private readonly PhotoPrismUploadService _photoPrismUploadService;
|
||||||
|
private readonly DispatcherTimer _galleryPromptTimer;
|
||||||
|
|
||||||
|
public bool IsPhotoProcessRunning { get; private set; }
|
||||||
|
|
||||||
|
// Events → MainWindow subscribes and updates UI
|
||||||
|
public event Action<string, string>? LoadingProgressChanged; // (statusText, countText)
|
||||||
|
public event Action? InitializationCompleted;
|
||||||
|
public event Action? GalleryPromptRequested;
|
||||||
|
public event Action? GalleryPromptDismissed;
|
||||||
|
|
||||||
|
|
||||||
|
public MainWindowViewModel(
|
||||||
|
Logger logger,
|
||||||
|
AppSettingsService appSettings,
|
||||||
|
PictureGalleryService pictureGalleryService,
|
||||||
|
CameraService cameraService,
|
||||||
|
PhotoPrismUploadService photoPrismUploadService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_appSettings = appSettings;
|
||||||
|
_pictureGalleryService = pictureGalleryService;
|
||||||
|
_cameraService = cameraService;
|
||||||
|
_photoPrismUploadService = photoPrismUploadService;
|
||||||
|
|
||||||
|
_galleryPromptTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
|
||||||
|
_galleryPromptTimer.Tick += (_, _) => DismissGalleryPrompt();
|
||||||
|
|
||||||
|
if (appSettings.PhotoPrismAutoUploadEnabled)
|
||||||
|
_ = InitializePhotoUploadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LoadThumbnailsAsync();
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error($"Initialization error: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
InitializationCompleted?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by MainWindow when the countdown timer fires and a photo should be taken.
|
||||||
|
/// Throws on camera error so MainWindow can show a message box.
|
||||||
|
/// </summary>
|
||||||
|
public void OnTimerElapsed()
|
||||||
|
{
|
||||||
|
IsPhotoProcessRunning = false;
|
||||||
|
_cameraService.TakePhoto();
|
||||||
|
ShowGalleryPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void StartPhotoProcess()
|
||||||
|
{
|
||||||
|
IsPhotoProcessRunning = true;
|
||||||
|
DismissGalleryPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void CancelPhotoProcess()
|
||||||
|
{
|
||||||
|
IsPhotoProcessRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void ShowGalleryPrompt()
|
||||||
|
{
|
||||||
|
GalleryPromptRequested?.Invoke();
|
||||||
|
_galleryPromptTimer.Stop();
|
||||||
|
_galleryPromptTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void DismissGalleryPrompt()
|
||||||
|
{
|
||||||
|
_galleryPromptTimer.Stop();
|
||||||
|
GalleryPromptDismissed?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task LoadThumbnailsAsync()
|
||||||
|
{
|
||||||
|
var pictureLocation = _appSettings.PictureLocation;
|
||||||
|
|
||||||
|
if (!Directory.Exists(pictureLocation))
|
||||||
|
{
|
||||||
|
LoadingProgressChanged?.Invoke("Keine Fotos gefunden", "0 Fotos");
|
||||||
|
await Task.Delay(1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] imageExtensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif"];
|
||||||
|
var totalCount = Directory.EnumerateFiles(pictureLocation!)
|
||||||
|
.Count(f => imageExtensions.Contains(Path.GetExtension(f).ToLowerInvariant()));
|
||||||
|
|
||||||
|
if (totalCount == 0)
|
||||||
|
{
|
||||||
|
LoadingProgressChanged?.Invoke("Keine Fotos gefunden", "Bereit für neue Aufnahmen!");
|
||||||
|
await Task.Delay(1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var photoLabel = totalCount == 1 ? "Foto" : "Fotos";
|
||||||
|
LoadingProgressChanged?.Invoke($"Lade {totalCount} {photoLabel}...", $"0 / {totalCount}");
|
||||||
|
await _pictureGalleryService.LoadThumbnailsToCache();
|
||||||
|
LoadingProgressChanged?.Invoke("Fotos erfolgreich geladen!", $"{totalCount} {photoLabel} bereit");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task InitializePhotoUploadAsync()
|
||||||
|
{
|
||||||
|
_logger.Info("PhotoPrism Auto-Upload aktiviert. Authentifiziere...");
|
||||||
|
var authSuccess = await _photoPrismUploadService.AuthenticateAsync();
|
||||||
|
if (authSuccess)
|
||||||
|
_logger.Info("PhotoPrism-Authentifizierung erfolgreich!");
|
||||||
|
else
|
||||||
|
_logger.Warning("PhotoPrism-Authentifizierung fehlgeschlagen.");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user