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 CamBooth.App.Core.AppSettings;
|
||||
@ -21,134 +20,113 @@ namespace CamBooth.App;
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
private IServiceProvider _serviceProvider;
|
||||
private IServiceProvider? _serviceProvider;
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
// Konfiguration laden
|
||||
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
|
||||
|
||||
var configBuilder = new ConfigurationBuilder()
|
||||
.SetBasePath(AppContext.BaseDirectory)
|
||||
.AddJsonFile("Core/AppSettings/app.settings.json", optional: false, reloadOnChange: true);
|
||||
var configuration = BuildConfiguration();
|
||||
var services = new ServiceCollection();
|
||||
|
||||
if (environment == "Development")
|
||||
{
|
||||
configBuilder.AddJsonFile("Core/AppSettings/app.settings.dev.json", optional: true, reloadOnChange: true);
|
||||
}
|
||||
RegisterServices(services, configuration);
|
||||
|
||||
var configuration = configBuilder.Build();
|
||||
_serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddSingleton<IConfiguration>(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>();
|
||||
StartBackgroundServices();
|
||||
|
||||
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();
|
||||
|
||||
// Starte PhotoPrism Upload-Service beim Start
|
||||
try
|
||||
{
|
||||
var uploadQueueService = _serviceProvider.GetRequiredService<PhotoPrismUploadQueueService>();
|
||||
uploadQueueService.Start();
|
||||
|
||||
// Scan für fehlgeschlagene Uploads beim Start
|
||||
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)
|
||||
{
|
||||
// Stoppe PhotoPrism UploadQueueService beim Beenden der App
|
||||
try
|
||||
{
|
||||
var uploadQueueService = _serviceProvider?.GetService<PhotoPrismUploadQueueService>();
|
||||
if (uploadQueueService != null)
|
||||
{
|
||||
uploadQueueService.StopAsync().Wait(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Fehler beim Stoppen des PhotoPrism UploadQueueService: {ex.Message}");
|
||||
}
|
||||
|
||||
// Dispose Service Provider, damit IDisposable-Services (z.B. CameraService) sauber beendet werden.
|
||||
try
|
||||
{
|
||||
if (_serviceProvider is IDisposable disposableProvider)
|
||||
{
|
||||
disposableProvider.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Fehler beim Dispose des ServiceProviders: {ex.Message}");
|
||||
}
|
||||
|
||||
base.OnExit(e);
|
||||
}
|
||||
_serviceProvider.GetRequiredService<MainWindow>().Show();
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_serviceProvider?.GetService<PhotoPrismUploadQueueService>()
|
||||
?.StopAsync().Wait(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error stopping upload service: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
(_serviceProvider as IDisposable)?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error disposing service provider: {ex.Message}");
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,425 +14,284 @@ namespace CamBooth.App.Features.Camera;
|
||||
|
||||
public class CameraService : IDisposable
|
||||
{
|
||||
private readonly AppSettingsService _appSettings;
|
||||
|
||||
private readonly Logger _logger;
|
||||
|
||||
private readonly PictureGalleryService _pictureGalleryService;
|
||||
|
||||
private readonly PhotoPrismUploadQueueService _photoPrismUploadQueueService;
|
||||
|
||||
private readonly ICanonAPI _APIHandler;
|
||||
|
||||
private CameraValue[] AvList;
|
||||
|
||||
private int BulbTime = 30;
|
||||
|
||||
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,
|
||||
AppSettingsService appSettings,
|
||||
PictureGalleryService pictureGalleryService,
|
||||
PhotoPrismUploadQueueService photoPrismUploadQueueService,
|
||||
ICanonAPI APIHandler)
|
||||
{
|
||||
this._logger = logger;
|
||||
this._appSettings = appSettings;
|
||||
this._pictureGalleryService = pictureGalleryService;
|
||||
this._photoPrismUploadQueueService = photoPrismUploadQueueService;
|
||||
this._APIHandler = APIHandler;
|
||||
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()
|
||||
{
|
||||
this.CloseSession();
|
||||
this.IsInit = false;
|
||||
this._APIHandler.Dispose();
|
||||
this._mainCamera?.Dispose();
|
||||
}
|
||||
|
||||
|
||||
public void ConnectCamera()
|
||||
{
|
||||
ErrorHandler.SevereErrorHappened += this.ErrorHandler_SevereErrorHappened;
|
||||
ErrorHandler.NonSevereErrorHappened += this.ErrorHandler_NonSevereErrorHappened;
|
||||
|
||||
try
|
||||
{
|
||||
this.RefreshCamera();
|
||||
|
||||
// Retry logic for camera detection (some systems need time to initialize)
|
||||
int maxRetries = 3;
|
||||
int retryDelay = 750; // milliseconds
|
||||
|
||||
for (int attempt = 0; attempt < maxRetries; attempt++)
|
||||
{
|
||||
if (this.CamList != null && this.CamList.Any())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (attempt < maxRetries - 1)
|
||||
{
|
||||
System.Threading.Thread.Sleep(retryDelay);
|
||||
this.RefreshCamera();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.CamList == null || !this.CamList.Any())
|
||||
{
|
||||
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));
|
||||
this._logger.Debug(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)
|
||||
{
|
||||
this._logger.Error($"Error connecting camera: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SetSettingSaveToComputer()
|
||||
{
|
||||
this._mainCamera.SetSetting(PropertyID.SaveTo, (int)SaveTo.Host);
|
||||
this._mainCamera.SetCapacity(4096, int.MaxValue);
|
||||
}
|
||||
|
||||
|
||||
public void CloseSession()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this._mainCamera != null && this._mainCamera.SessionOpen)
|
||||
{
|
||||
this._mainCamera.CloseSession();
|
||||
this._logger.Info("Camera session closed");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.Error($"Error closing camera session: {ex.Message}");
|
||||
}
|
||||
|
||||
// AvCoBox.Items.Clear();
|
||||
// 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()
|
||||
{
|
||||
try
|
||||
{
|
||||
this._mainCamera.TakePhoto();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ReportError(ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
public async Task PrepareFocusAsync(int focusTimeoutMs = 1500)
|
||||
{
|
||||
if (this._mainCamera is not EOSDigital.API.Camera sdkCamera)
|
||||
{
|
||||
await Task.Delay(200);
|
||||
return;
|
||||
}
|
||||
|
||||
var focusCompleted = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
void FocusStateChanged(EOSDigital.API.Camera sender, StateEventID eventId, int parameter)
|
||||
{
|
||||
if (eventId == StateEventID.AfResult)
|
||||
{
|
||||
focusCompleted.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
sdkCamera.StateChanged += FocusStateChanged;
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Halfway));
|
||||
var completedTask = await Task.WhenAny(focusCompleted.Task, Task.Delay(focusTimeoutMs));
|
||||
if (completedTask != focusCompleted.Task)
|
||||
{
|
||||
this._logger.Info("Autofocus timeout reached, continuing with countdown.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ReportError(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ReportError(ex.Message);
|
||||
}
|
||||
|
||||
sdkCamera.StateChanged -= FocusStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region API Events
|
||||
|
||||
// private void APIHandler_CameraAdded(CanonAPI sender)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// ReportError(ex.Message, false);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
private void MainCamera_StateChanged(EOSDigital.API.Camera sender, StateEventID eventID, int parameter)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (eventID == StateEventID.Shutdown && this.IsInit)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() => this.CloseSession());
|
||||
|
||||
//Dispatcher.Invoke((Action)delegate { CloseSession(); });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ReportError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// private void MainCamera_ProgressChanged(object sender, int progress)
|
||||
// {
|
||||
// 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");
|
||||
try
|
||||
{
|
||||
Info.FileName = $"img_{Guid.NewGuid().ToString()}.jpg";
|
||||
sender.DownloadFile(Info, this._appSettings.PictureLocation);
|
||||
var savedPhotoPath = Path.Combine(this._appSettings.PictureLocation, Info.FileName);
|
||||
this._logger.Info("Download complete: " + savedPhotoPath);
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() => {
|
||||
this._pictureGalleryService.IncrementNewPhotoCount();
|
||||
this._pictureGalleryService.LoadThumbnailsToCache();
|
||||
});
|
||||
|
||||
// Füge neues Foto zur PhotoPrism Upload-Queue hinzu (wenn Auto-Upload aktiviert)
|
||||
this._photoPrismUploadQueueService.QueueNewPhoto(savedPhotoPath);
|
||||
this._logger.Info($"Foto zur PhotoPrism Upload-Queue hinzugefügt: {Info.FileName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ReportError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ErrorHandler_NonSevereErrorHappened(object sender, ErrorCode ex)
|
||||
{
|
||||
this.ReportError($"SDK Error code: {ex} ({((int)ex).ToString("X")})");
|
||||
}
|
||||
|
||||
|
||||
private void ErrorHandler_SevereErrorHappened(object sender, Exception ex)
|
||||
{
|
||||
this.ReportError(ex.Message);
|
||||
}
|
||||
|
||||
#endregion
|
||||
private readonly AppSettingsService _appSettings;
|
||||
private readonly Logger _logger;
|
||||
private readonly PictureGalleryService _pictureGalleryService;
|
||||
private readonly PhotoPrismUploadQueueService _photoPrismUploadQueueService;
|
||||
private readonly ICanonAPI _canonApi;
|
||||
|
||||
private ICamera? _mainCamera;
|
||||
private List<ICamera>? _camList;
|
||||
private bool _isConnected;
|
||||
|
||||
/// <summary>Fires whenever the camera delivers a new live-view frame.</summary>
|
||||
public event Action<Stream>? LiveViewUpdated;
|
||||
|
||||
public bool IsConnected => _isConnected && _mainCamera?.SessionOpen == true;
|
||||
|
||||
|
||||
public CameraService(
|
||||
Logger logger,
|
||||
AppSettingsService appSettings,
|
||||
PictureGalleryService pictureGalleryService,
|
||||
PhotoPrismUploadQueueService photoPrismUploadQueueService,
|
||||
ICanonAPI canonApi)
|
||||
{
|
||||
_logger = logger;
|
||||
_appSettings = appSettings;
|
||||
_pictureGalleryService = pictureGalleryService;
|
||||
_photoPrismUploadQueueService = photoPrismUploadQueueService;
|
||||
_canonApi = canonApi;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CloseSession();
|
||||
_canonApi.Dispose();
|
||||
_mainCamera?.Dispose();
|
||||
}
|
||||
|
||||
|
||||
public void ConnectCamera()
|
||||
{
|
||||
ErrorHandler.SevereErrorHappened += ErrorHandler_SevereErrorHappened;
|
||||
ErrorHandler.NonSevereErrorHappened += ErrorHandler_NonSevereErrorHappened;
|
||||
|
||||
try
|
||||
{
|
||||
RefreshCameraList();
|
||||
|
||||
const int maxRetries = 3;
|
||||
const int retryDelayMs = 750;
|
||||
|
||||
for (int attempt = 0; attempt < maxRetries; attempt++)
|
||||
{
|
||||
if (_camList?.Any() == true) break;
|
||||
|
||||
if (attempt < maxRetries - 1)
|
||||
{
|
||||
System.Threading.Thread.Sleep(retryDelayMs);
|
||||
RefreshCameraList();
|
||||
}
|
||||
}
|
||||
|
||||
if (_camList?.Any() != true)
|
||||
throw new InvalidOperationException("No cameras found after multiple attempts.");
|
||||
|
||||
_mainCamera = _camList[0];
|
||||
_logger.Info($"Camera found: {_mainCamera.DeviceName}");
|
||||
|
||||
OpenSession();
|
||||
SetSaveToComputer();
|
||||
StartLiveView();
|
||||
_isConnected = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Error connecting camera: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void CloseSession()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_mainCamera?.SessionOpen == true)
|
||||
{
|
||||
_mainCamera.CloseSession();
|
||||
_logger.Info("Camera session closed");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Error closing camera session: {ex.Message}");
|
||||
}
|
||||
|
||||
_isConnected = false;
|
||||
}
|
||||
|
||||
|
||||
public void TakePhoto()
|
||||
{
|
||||
if (_mainCamera == null) throw new InvalidOperationException("Camera not connected.");
|
||||
_mainCamera.TakePhoto();
|
||||
}
|
||||
|
||||
|
||||
public async Task PrepareFocusAsync(int focusTimeoutMs = 1500)
|
||||
{
|
||||
if (_mainCamera is not EOSDigital.API.Camera sdkCamera)
|
||||
{
|
||||
await Task.Delay(200);
|
||||
return;
|
||||
}
|
||||
|
||||
var focusCompleted = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
void FocusStateChanged(EOSDigital.API.Camera sender, StateEventID eventId, int parameter)
|
||||
{
|
||||
if (eventId == StateEventID.AfResult)
|
||||
focusCompleted.TrySetResult(true);
|
||||
}
|
||||
|
||||
sdkCamera.StateChanged += FocusStateChanged;
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Halfway));
|
||||
var completed = await Task.WhenAny(focusCompleted.Task, Task.Delay(focusTimeoutMs));
|
||||
if (completed != focusCompleted.Task)
|
||||
_logger.Info("Autofocus timeout reached, continuing.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF));
|
||||
}
|
||||
catch (Exception ex) { _logger.Error(ex.Message); }
|
||||
|
||||
sdkCamera.StateChanged -= FocusStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void RefreshCameraList() => _camList = _canonApi.GetCameraList();
|
||||
|
||||
|
||||
private void OpenSession()
|
||||
{
|
||||
if (_mainCamera == null)
|
||||
throw new InvalidOperationException("Camera reference is null.");
|
||||
|
||||
if (_mainCamera.SessionOpen)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (eventID == StateEventID.Shutdown && _isConnected)
|
||||
Application.Current.Dispatcher.Invoke(CloseSession);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void MainCamera_DownloadReady(ICamera sender, IDownloadInfo info)
|
||||
{
|
||||
_logger.Info("Download ready");
|
||||
try
|
||||
{
|
||||
info.FileName = $"img_{Guid.NewGuid()}.jpg";
|
||||
sender.DownloadFile(info, _appSettings.PictureLocation);
|
||||
var savedPath = Path.Combine(_appSettings.PictureLocation!, info.FileName);
|
||||
_logger.Info($"Download complete: {savedPath}");
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
_pictureGalleryService.IncrementNewPhotoCount();
|
||||
_pictureGalleryService.LoadThumbnailsToCache();
|
||||
});
|
||||
|
||||
_photoPrismUploadQueueService.QueueNewPhoto(savedPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ErrorHandler_NonSevereErrorHappened(object sender, ErrorCode ex) =>
|
||||
_logger.Error($"SDK Error: {ex} (0x{(int)ex:X})");
|
||||
|
||||
|
||||
private void ErrorHandler_SevereErrorHappened(object sender, Exception ex) =>
|
||||
_logger.Error(ex.Message);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
using CamBooth.App.Core.AppSettings;
|
||||
using CamBooth.App.Core.Logging;
|
||||
using CamBooth.App.Features.Camera;
|
||||
|
||||
@ -12,87 +11,69 @@ using EOSDigital.API;
|
||||
|
||||
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 Logger _logger;
|
||||
|
||||
private readonly ImageBrush bgbrush = new();
|
||||
|
||||
private readonly Action<BitmapImage> SetImageAction;
|
||||
private readonly CameraService _cameraService;
|
||||
private readonly Logger _logger;
|
||||
private readonly ImageBrush _bgBrush = new();
|
||||
|
||||
|
||||
public LiveViewPage(Logger logger, AppSettingsService appSettings, CameraService cameraService)
|
||||
{
|
||||
this._logger = logger;
|
||||
this._appSettings = appSettings;
|
||||
this._cameraService = cameraService;
|
||||
this.InitializeComponent();
|
||||
this.SetImageAction = img => { this.bgbrush.ImageSource = img; };
|
||||
|
||||
// Configure the image brush
|
||||
this.bgbrush.Stretch = Stretch.UniformToFill;
|
||||
this.bgbrush.AlignmentX = AlignmentX.Center;
|
||||
this.bgbrush.AlignmentY = AlignmentY.Center;
|
||||
|
||||
this.LVCanvas.Background = this.bgbrush;
|
||||
|
||||
// Apply horizontal flip on the Canvas using RenderTransform
|
||||
TransformGroup transformGroup = new();
|
||||
transformGroup.Children.Add(new ScaleTransform { ScaleX = -1, ScaleY = 1 });
|
||||
transformGroup.Children.Add(new TranslateTransform { X = 1, Y = 0 });
|
||||
this.LVCanvas.RenderTransform = transformGroup;
|
||||
this.LVCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
|
||||
|
||||
try
|
||||
{
|
||||
cameraService.ConnectCamera();
|
||||
|
||||
// Verify that camera session is open before subscribing to events
|
||||
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)
|
||||
{
|
||||
this._logger.Error($"Failed to initialize LiveViewPage: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
public LiveViewPage(Logger logger, CameraService cameraService)
|
||||
{
|
||||
_logger = logger;
|
||||
_cameraService = cameraService;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
_bgBrush.Stretch = Stretch.UniformToFill;
|
||||
_bgBrush.AlignmentX = AlignmentX.Center;
|
||||
_bgBrush.AlignmentY = AlignmentY.Center;
|
||||
LVCanvas.Background = _bgBrush;
|
||||
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new ScaleTransform { ScaleX = -1, ScaleY = 1 });
|
||||
transformGroup.Children.Add(new TranslateTransform { X = 1, Y = 0 });
|
||||
LVCanvas.RenderTransform = transformGroup;
|
||||
LVCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
|
||||
|
||||
try
|
||||
{
|
||||
cameraService.ConnectCamera();
|
||||
cameraService.LiveViewUpdated += OnLiveViewUpdated;
|
||||
_logger.Info("LiveViewPage initialized successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Failed to initialize LiveViewPage: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void MainCamera_OnLiveViewUpdated(ICamera sender, Stream img)
|
||||
{
|
||||
try
|
||||
{
|
||||
using WrapStream s = new(img);
|
||||
img.Position = 0;
|
||||
BitmapImage EvfImage = new();
|
||||
EvfImage.BeginInit();
|
||||
EvfImage.StreamSource = s;
|
||||
EvfImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
EvfImage.EndInit();
|
||||
EvfImage.Freeze();
|
||||
Application.Current.Dispatcher.BeginInvoke(this.SetImageAction, EvfImage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.Error(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this._cameraService.Dispose();
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_cameraService.LiveViewUpdated -= OnLiveViewUpdated;
|
||||
_cameraService.Dispose();
|
||||
}
|
||||
|
||||
|
||||
private void OnLiveViewUpdated(Stream img)
|
||||
{
|
||||
try
|
||||
{
|
||||
using WrapStream s = new(img);
|
||||
img.Position = 0;
|
||||
var evfImage = new BitmapImage();
|
||||
evfImage.BeginInit();
|
||||
evfImage.StreamSource = s;
|
||||
evfImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
evfImage.EndInit();
|
||||
evfImage.Freeze();
|
||||
Application.Current.Dispatcher.BeginInvoke(() => _bgBrush.ImageSource = evfImage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
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