Add uploader advanced settings and live status
This commit is contained in:
@@ -10,7 +10,11 @@
|
|||||||
<StackPanel Grid.Column="0" Spacing="12" MaxWidth="420">
|
<StackPanel Grid.Column="0" Spacing="12" MaxWidth="420">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||||||
<Image Source="avares://PhotoboothUploader/Assets/logo.png" Width="32" Height="32" />
|
<Image Source="avares://PhotoboothUploader/Assets/logo.png" Width="32" Height="32" />
|
||||||
<TextBlock Text="Die Fotospiel.App - Photobooth Uploader" FontSize="18" FontWeight="SemiBold" />
|
<TextBlock x:Name="TitleText"
|
||||||
|
Text="Die Fotospiel.App - Photobooth Uploader"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
PointerPressed="TitleText_PointerPressed" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Border Background="#1F000000" Padding="12" CornerRadius="8">
|
<Border Background="#1F000000" Padding="12" CornerRadius="8">
|
||||||
@@ -23,8 +27,11 @@
|
|||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<TextBlock Text="Gib den 6-stelligen Verbindungscode ein." TextWrapping="Wrap" />
|
<TextBlock Text="Gib den 6-stelligen Verbindungscode ein." TextWrapping="Wrap" />
|
||||||
<TextBox x:Name="CodeBox" MaxLength="6" Watermark="123456" />
|
<TextBox x:Name="CodeBox" MaxLength="6" Watermark="123456" TextChanged="CodeBox_TextChanged" />
|
||||||
<Button x:Name="ConnectButton" Content="Verbinden" Click="ConnectButton_Click" />
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Button x:Name="ConnectButton" Content="Verbinden" Click="ConnectButton_Click" />
|
||||||
|
<Button x:Name="ReconnectButton" Content="Erneut verbinden" Click="ReconnectButton_Click" IsEnabled="False" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Spacing="6">
|
<StackPanel Spacing="6">
|
||||||
<TextBlock Text="Upload-Ordner" FontWeight="SemiBold" />
|
<TextBlock Text="Upload-Ordner" FontWeight="SemiBold" />
|
||||||
@@ -34,6 +41,17 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<ToggleSwitch x:Name="QuietToggle" Content="Ruhiger Modus (nur Fehler anzeigen)" />
|
<ToggleSwitch x:Name="QuietToggle" Content="Ruhiger Modus (nur Fehler anzeigen)" />
|
||||||
|
|
||||||
|
<Border x:Name="AdvancedPanel" Background="#1F000000" Padding="12" CornerRadius="8" IsVisible="False">
|
||||||
|
<StackPanel Spacing="6">
|
||||||
|
<TextBlock Text="Erweiterte Einstellungen" FontWeight="SemiBold" />
|
||||||
|
<TextBlock Text="Basis-URL" />
|
||||||
|
<TextBox x:Name="BaseUrlBox" Watermark="https://fotospiel.app" />
|
||||||
|
<TextBlock Text="Max. parallele Uploads" />
|
||||||
|
<TextBox x:Name="MaxUploadsBox" Watermark="2" />
|
||||||
|
<Button x:Name="SaveAdvancedButton" Content="Speichern" Click="SaveAdvancedButton_Click" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Grid.Column="2" Spacing="12" MaxWidth="380">
|
<StackPanel Grid.Column="2" Spacing="12" MaxWidth="380">
|
||||||
@@ -43,6 +61,7 @@
|
|||||||
<TextBlock x:Name="StatusText" Text="Nicht verbunden." TextWrapping="Wrap" />
|
<TextBlock x:Name="StatusText" Text="Nicht verbunden." TextWrapping="Wrap" />
|
||||||
<TextBlock x:Name="LastUploadText" Text="Letzter Upload: —" />
|
<TextBlock x:Name="LastUploadText" Text="Letzter Upload: —" />
|
||||||
<TextBlock x:Name="QueueStatusText" Text="Warteschlange: 0 · Läuft: 0 · Fehlgeschlagen: 0" />
|
<TextBlock x:Name="QueueStatusText" Text="Warteschlange: 0 · Läuft: 0 · Fehlgeschlagen: 0" />
|
||||||
|
<TextBlock x:Name="LiveStatusText" Text="Live: —" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
@@ -52,6 +71,8 @@
|
|||||||
<TextBlock x:Name="EventNameText" Text="Event: —" TextWrapping="Wrap" />
|
<TextBlock x:Name="EventNameText" Text="Event: —" TextWrapping="Wrap" />
|
||||||
<TextBlock x:Name="BaseUrlText" Text="Basis-URL: —" TextWrapping="Wrap" />
|
<TextBlock x:Name="BaseUrlText" Text="Basis-URL: —" TextWrapping="Wrap" />
|
||||||
<TextBlock x:Name="VersionText" Text="App-Version: —" />
|
<TextBlock x:Name="VersionText" Text="App-Version: —" />
|
||||||
|
<TextBlock x:Name="FolderHealthText" Text="Ordner: —" TextWrapping="Wrap" />
|
||||||
|
<TextBlock x:Name="DiskFreeText" Text="Freier Speicher: —" TextWrapping="Wrap" />
|
||||||
<TextBlock x:Name="LastErrorText" Text="Letzter Fehler: —" TextWrapping="Wrap" />
|
<TextBlock x:Name="LastErrorText" Text="Letzter Fehler: —" TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
@@ -29,6 +30,9 @@ public partial class MainWindow : Window
|
|||||||
private int _queuedCount;
|
private int _queuedCount;
|
||||||
private int _uploadingCount;
|
private int _uploadingCount;
|
||||||
private int _failedCount;
|
private int _failedCount;
|
||||||
|
private DateTimeOffset? _lastSuccessAt;
|
||||||
|
private bool _advancedVisible;
|
||||||
|
private readonly DispatcherTimer _liveTimer = new();
|
||||||
|
|
||||||
public ObservableCollection<UploadItem> RecentUploads { get; } = new();
|
public ObservableCollection<UploadItem> RecentUploads { get; } = new();
|
||||||
|
|
||||||
@@ -37,15 +41,60 @@ public partial class MainWindow : Window
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_settings = _settingsStore.Load();
|
_settings = _settingsStore.Load();
|
||||||
_settings.BaseUrl = NormalizeBaseUrl(_settings.BaseUrl) ?? DefaultBaseUrl;
|
_settings.BaseUrl = NormalizeBaseUrl(_settings.BaseUrl) ?? DefaultBaseUrl;
|
||||||
|
if (_settings.MaxConcurrentUploads <= 0)
|
||||||
|
{
|
||||||
|
_settings.MaxConcurrentUploads = 2;
|
||||||
|
}
|
||||||
_userAgent = $"FotospielPhotoboothUploader/{GetAppVersion()}";
|
_userAgent = $"FotospielPhotoboothUploader/{GetAppVersion()}";
|
||||||
_client = new PhotoboothConnectClient(_settings.BaseUrl, _userAgent);
|
_client = new PhotoboothConnectClient(_settings.BaseUrl, _userAgent);
|
||||||
_uploadService.Configure(_userAgent);
|
_uploadService.Configure(_userAgent);
|
||||||
_settingsStore.Save(_settings);
|
_settingsStore.Save(_settings);
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
_liveTimer.Interval = TimeSpan.FromSeconds(30);
|
||||||
|
_liveTimer.Tick += (_, _) => UpdateLiveStatus();
|
||||||
|
_liveTimer.Start();
|
||||||
ApplySettings();
|
ApplySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ConnectButton_Click(object? sender, RoutedEventArgs e)
|
private async void ConnectButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var code = (CodeBox.Text ?? string.Empty).Trim();
|
||||||
|
|
||||||
|
if (!IsValidCode(code))
|
||||||
|
{
|
||||||
|
StatusText.Text = "Bitte einen gültigen 6-stelligen Code eingeben.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectButton.IsEnabled = false;
|
||||||
|
ReconnectButton.IsEnabled = false;
|
||||||
|
StatusText.Text = "Verbinde...";
|
||||||
|
|
||||||
|
await RedeemCodeAsync(code);
|
||||||
|
|
||||||
|
ConnectButton.IsEnabled = true;
|
||||||
|
ReconnectButton.IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ReconnectButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var code = (CodeBox.Text ?? string.Empty).Trim();
|
||||||
|
|
||||||
|
if (!IsValidCode(code))
|
||||||
|
{
|
||||||
|
StatusText.Text = "Bitte einen gültigen 6-stelligen Code eingeben.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReconnectButton.IsEnabled = false;
|
||||||
|
StatusText.Text = "Verbindung erneuern...";
|
||||||
|
|
||||||
|
await RedeemCodeAsync(code);
|
||||||
|
|
||||||
|
ReconnectButton.IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RedeemCodeAsync(string code)
|
||||||
{
|
{
|
||||||
var normalizedBaseUrl = NormalizeBaseUrl(_settings.BaseUrl);
|
var normalizedBaseUrl = NormalizeBaseUrl(_settings.BaseUrl);
|
||||||
if (normalizedBaseUrl is null)
|
if (normalizedBaseUrl is null)
|
||||||
@@ -61,24 +110,12 @@ public partial class MainWindow : Window
|
|||||||
_client = new PhotoboothConnectClient(_settings.BaseUrl, _userAgent);
|
_client = new PhotoboothConnectClient(_settings.BaseUrl, _userAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
var code = (CodeBox.Text ?? string.Empty).Trim();
|
|
||||||
|
|
||||||
if (code.Length != 6 || code.Any(ch => ch is < '0' or > '9'))
|
|
||||||
{
|
|
||||||
StatusText.Text = "Bitte einen gültigen 6-stelligen Code eingeben.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectButton.IsEnabled = false;
|
|
||||||
StatusText.Text = "Verbinde...";
|
|
||||||
|
|
||||||
var response = await _client.RedeemAsync(code);
|
var response = await _client.RedeemAsync(code);
|
||||||
|
|
||||||
if (response.Data is null)
|
if (response.Data is null)
|
||||||
{
|
{
|
||||||
StatusText.Text = response.Message ?? "Verbindung fehlgeschlagen.";
|
StatusText.Text = response.Message ?? "Verbindung fehlgeschlagen.";
|
||||||
SetLastError(response.Message ?? "Verbindung fehlgeschlagen.");
|
SetLastError(response.Message ?? "Verbindung fehlgeschlagen.");
|
||||||
ConnectButton.IsEnabled = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,9 +130,9 @@ public partial class MainWindow : Window
|
|||||||
StatusText.Text = "Verbunden. Upload bereit.";
|
StatusText.Text = "Verbunden. Upload bereit.";
|
||||||
PickFolderButton.IsEnabled = true;
|
PickFolderButton.IsEnabled = true;
|
||||||
TestUploadButton.IsEnabled = true;
|
TestUploadButton.IsEnabled = true;
|
||||||
|
ReconnectButton.IsEnabled = true;
|
||||||
UpdateDiagnostics();
|
UpdateDiagnostics();
|
||||||
StartUploadPipelineIfReady();
|
StartUploadPipelineIfReady();
|
||||||
ConnectButton.IsEnabled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void PickFolderButton_Click(object? sender, RoutedEventArgs e)
|
private async void PickFolderButton_Click(object? sender, RoutedEventArgs e)
|
||||||
@@ -119,6 +156,7 @@ public partial class MainWindow : Window
|
|||||||
_settingsStore.Save(_settings);
|
_settingsStore.Save(_settings);
|
||||||
|
|
||||||
FolderText.Text = localPath;
|
FolderText.Text = localPath;
|
||||||
|
UpdateFolderHealth();
|
||||||
StartUploadPipelineIfReady();
|
StartUploadPipelineIfReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,15 +167,20 @@ public partial class MainWindow : Window
|
|||||||
FolderText.Text = _settings.WatchFolder;
|
FolderText.Text = _settings.WatchFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BaseUrlBox.Text = _settings.BaseUrl ?? DefaultBaseUrl;
|
||||||
|
MaxUploadsBox.Text = _settings.MaxConcurrentUploads.ToString();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
|
if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
|
||||||
{
|
{
|
||||||
StatusText.Text = "Verbunden. Upload bereit.";
|
StatusText.Text = "Verbunden. Upload bereit.";
|
||||||
PickFolderButton.IsEnabled = true;
|
PickFolderButton.IsEnabled = true;
|
||||||
TestUploadButton.IsEnabled = true;
|
TestUploadButton.IsEnabled = true;
|
||||||
|
ReconnectButton.IsEnabled = true;
|
||||||
StartUploadPipelineIfReady();
|
StartUploadPipelineIfReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateCountersText();
|
UpdateCountersText();
|
||||||
|
UpdateFolderHealth();
|
||||||
UpdateDiagnostics();
|
UpdateDiagnostics();
|
||||||
UpdateSteps();
|
UpdateSteps();
|
||||||
}
|
}
|
||||||
@@ -150,6 +193,7 @@ public partial class MainWindow : Window
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResetCounters();
|
||||||
_uploadService.Start(_settings, OnQueued, OnUploading, OnSuccess, OnFailure);
|
_uploadService.Start(_settings, OnQueued, OnUploading, OnSuccess, OnFailure);
|
||||||
StartWatcher(_settings.WatchFolder);
|
StartWatcher(_settings.WatchFolder);
|
||||||
UpdateSteps();
|
UpdateSteps();
|
||||||
@@ -204,7 +248,7 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
private void OnQueued(string path)
|
private void OnQueued(string path)
|
||||||
{
|
{
|
||||||
_queuedCount = Math.Max(0, _queuedCount + 1);
|
Interlocked.Increment(ref _queuedCount);
|
||||||
UpdateUpload(path, UploadStatus.Queued);
|
UpdateUpload(path, UploadStatus.Queued);
|
||||||
UpdateStatusIfAllowed($"Wartet: {Path.GetFileName(path)}", false);
|
UpdateStatusIfAllowed($"Wartet: {Path.GetFileName(path)}", false);
|
||||||
UpdateCountersText();
|
UpdateCountersText();
|
||||||
@@ -212,8 +256,8 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
private void OnUploading(string path)
|
private void OnUploading(string path)
|
||||||
{
|
{
|
||||||
_queuedCount = Math.Max(0, _queuedCount - 1);
|
Interlocked.Decrement(ref _queuedCount);
|
||||||
_uploadingCount = Math.Max(0, _uploadingCount + 1);
|
Interlocked.Increment(ref _uploadingCount);
|
||||||
UpdateUpload(path, UploadStatus.Uploading);
|
UpdateUpload(path, UploadStatus.Uploading);
|
||||||
UpdateStatusIfAllowed($"Upload läuft: {Path.GetFileName(path)}", false);
|
UpdateStatusIfAllowed($"Upload läuft: {Path.GetFileName(path)}", false);
|
||||||
UpdateCountersText();
|
UpdateCountersText();
|
||||||
@@ -222,17 +266,19 @@ public partial class MainWindow : Window
|
|||||||
private void OnSuccess(string path)
|
private void OnSuccess(string path)
|
||||||
{
|
{
|
||||||
_failedPaths.Remove(path);
|
_failedPaths.Remove(path);
|
||||||
_uploadingCount = Math.Max(0, _uploadingCount - 1);
|
Interlocked.Decrement(ref _uploadingCount);
|
||||||
|
_lastSuccessAt = DateTimeOffset.Now;
|
||||||
UpdateUpload(path, UploadStatus.Success);
|
UpdateUpload(path, UploadStatus.Success);
|
||||||
UpdateStatusIfAllowed($"Hochgeladen: {Path.GetFileName(path)}", false);
|
UpdateStatusIfAllowed($"Hochgeladen: {Path.GetFileName(path)}", false);
|
||||||
UpdateCountersText();
|
UpdateCountersText();
|
||||||
|
UpdateLiveStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnFailure(string path)
|
private void OnFailure(string path)
|
||||||
{
|
{
|
||||||
_failedPaths.Add(path);
|
_failedPaths.Add(path);
|
||||||
_uploadingCount = Math.Max(0, _uploadingCount - 1);
|
Interlocked.Decrement(ref _uploadingCount);
|
||||||
_failedCount = Math.Max(0, _failedCount + 1);
|
Interlocked.Increment(ref _failedCount);
|
||||||
UpdateUpload(path, UploadStatus.Failed);
|
UpdateUpload(path, UploadStatus.Failed);
|
||||||
UpdateStatusIfAllowed($"Upload fehlgeschlagen: {Path.GetFileName(path)}", true);
|
UpdateStatusIfAllowed($"Upload fehlgeschlagen: {Path.GetFileName(path)}", true);
|
||||||
SetLastError($"Upload fehlgeschlagen: {Path.GetFileName(path)}");
|
SetLastError($"Upload fehlgeschlagen: {Path.GetFileName(path)}");
|
||||||
@@ -298,8 +344,8 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
|
|
||||||
_failedPaths.Clear();
|
_failedPaths.Clear();
|
||||||
_failedCount = Math.Max(0, _failedCount - retried);
|
Interlocked.Add(ref _failedCount, -retried);
|
||||||
_queuedCount = Math.Max(0, _queuedCount + retried);
|
Interlocked.Add(ref _queuedCount, retried);
|
||||||
UpdateRetryButton();
|
UpdateRetryButton();
|
||||||
UpdateCountersText();
|
UpdateCountersText();
|
||||||
}
|
}
|
||||||
@@ -315,6 +361,53 @@ public partial class MainWindow : Window
|
|||||||
StepReadyText.Text = ready ? "3. Upload läuft ✓" : "3. Upload läuft";
|
StepReadyText.Text = ready ? "3. Upload läuft ✓" : "3. Upload läuft";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CodeBox_TextChanged(object? sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var code = (CodeBox.Text ?? string.Empty).Trim();
|
||||||
|
var enabled = IsValidCode(code);
|
||||||
|
ReconnectButton.IsEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TitleText_PointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.ClickCount < 2)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_advancedVisible = !_advancedVisible;
|
||||||
|
AdvancedPanel.IsVisible = _advancedVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveAdvancedButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var normalizedBaseUrl = NormalizeBaseUrl(BaseUrlBox.Text);
|
||||||
|
if (normalizedBaseUrl is null)
|
||||||
|
{
|
||||||
|
UpdateStatus("Ungültige Basis-URL.");
|
||||||
|
SetLastError("Ungültige Basis-URL.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(MaxUploadsBox.Text, out var maxUploads) || maxUploads < 1 || maxUploads > 5)
|
||||||
|
{
|
||||||
|
UpdateStatus("Max. parallele Uploads muss zwischen 1 und 5 liegen.");
|
||||||
|
SetLastError("Ungültige Parallel-Uploads.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_settings.BaseUrl = normalizedBaseUrl;
|
||||||
|
_settings.MaxConcurrentUploads = maxUploads;
|
||||||
|
_settingsStore.Save(_settings);
|
||||||
|
|
||||||
|
_client = new PhotoboothConnectClient(_settings.BaseUrl, _userAgent);
|
||||||
|
_uploadService.Configure(_userAgent);
|
||||||
|
UpdateDiagnostics();
|
||||||
|
UpdateFolderHealth();
|
||||||
|
RestartUploadPipeline();
|
||||||
|
UpdateStatus("Einstellungen gespeichert.");
|
||||||
|
}
|
||||||
|
|
||||||
private async void TestUploadButton_Click(object? sender, RoutedEventArgs e)
|
private async void TestUploadButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_settings.UploadUrl))
|
if (string.IsNullOrWhiteSpace(_settings.UploadUrl))
|
||||||
@@ -346,6 +439,113 @@ public partial class MainWindow : Window
|
|||||||
return tempPath;
|
return tempPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ResetCounters()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _queuedCount, 0);
|
||||||
|
Interlocked.Exchange(ref _uploadingCount, 0);
|
||||||
|
Interlocked.Exchange(ref _failedCount, 0);
|
||||||
|
_failedPaths.Clear();
|
||||||
|
_lastSuccessAt = null;
|
||||||
|
UpdateCountersText();
|
||||||
|
UpdateRetryButton();
|
||||||
|
UpdateLiveStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestartUploadPipeline()
|
||||||
|
{
|
||||||
|
_uploadService.Stop();
|
||||||
|
StartUploadPipelineIfReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFolderHealth()
|
||||||
|
{
|
||||||
|
var folder = _settings.WatchFolder;
|
||||||
|
if (string.IsNullOrWhiteSpace(folder))
|
||||||
|
{
|
||||||
|
FolderHealthText.Text = "Ordner: —";
|
||||||
|
DiskFreeText.Text = "Freier Speicher: —";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(folder))
|
||||||
|
{
|
||||||
|
FolderHealthText.Text = "Ordner: fehlt";
|
||||||
|
DiskFreeText.Text = "Freier Speicher: —";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = Directory.EnumerateFileSystemEntries(folder).FirstOrDefault();
|
||||||
|
FolderHealthText.Text = "Ordner: OK";
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
FolderHealthText.Text = "Ordner: Keine Berechtigung";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
FolderHealthText.Text = "Ordner: Fehler";
|
||||||
|
}
|
||||||
|
|
||||||
|
DiskFreeText.Text = $"Freier Speicher: {FormatDiskFree(folder)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateLiveStatus()
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
if (_lastSuccessAt is null)
|
||||||
|
{
|
||||||
|
LiveStatusText.Text = "Live: —";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var age = DateTimeOffset.Now - _lastSuccessAt.Value;
|
||||||
|
var isLive = age <= TimeSpan.FromMinutes(5);
|
||||||
|
var label = isLive ? "Live: Ja" : "Live: Nein";
|
||||||
|
LiveStatusText.Text = $"{label} (letzter Upload {FormatRelativeAge(age)})";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatRelativeAge(TimeSpan age)
|
||||||
|
{
|
||||||
|
if (age.TotalMinutes < 1)
|
||||||
|
{
|
||||||
|
return "gerade eben";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (age.TotalHours < 1)
|
||||||
|
{
|
||||||
|
return $"vor {Math.Round(age.TotalMinutes)} min";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (age.TotalDays < 1)
|
||||||
|
{
|
||||||
|
return $"vor {Math.Round(age.TotalHours)} h";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"vor {Math.Round(age.TotalDays)} d";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatDiskFree(string folder)
|
||||||
|
{
|
||||||
|
var root = Path.GetPathRoot(folder);
|
||||||
|
if (string.IsNullOrWhiteSpace(root))
|
||||||
|
{
|
||||||
|
return "—";
|
||||||
|
}
|
||||||
|
|
||||||
|
var drive = new DriveInfo(root);
|
||||||
|
if (!drive.IsReady)
|
||||||
|
{
|
||||||
|
return "—";
|
||||||
|
}
|
||||||
|
|
||||||
|
var freeGb = drive.AvailableFreeSpace / (1024d * 1024d * 1024d);
|
||||||
|
return $"{freeGb:0.0} GB";
|
||||||
|
}
|
||||||
|
|
||||||
private string? ResolveUploadUrl(string? uploadUrl)
|
private string? ResolveUploadUrl(string? uploadUrl)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(uploadUrl))
|
if (string.IsNullOrWhiteSpace(uploadUrl))
|
||||||
@@ -388,7 +588,10 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
QueueStatusText.Text = $"Warteschlange: {_queuedCount} · Läuft: {_uploadingCount} · Fehlgeschlagen: {_failedCount}";
|
var queued = Volatile.Read(ref _queuedCount);
|
||||||
|
var uploading = Volatile.Read(ref _uploadingCount);
|
||||||
|
var failed = Volatile.Read(ref _failedCount);
|
||||||
|
QueueStatusText.Text = $"Warteschlange: {Math.Max(0, queued)} · Läuft: {Math.Max(0, uploading)} · Fehlgeschlagen: {Math.Max(0, failed)}";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,4 +642,9 @@ public partial class MainWindow : Window
|
|||||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||||
return version is null ? "0.0.0" : version.ToString();
|
return version is null ? "0.0.0" : version.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsValidCode(string code)
|
||||||
|
{
|
||||||
|
return code.Length == 6 && code.All(ch => ch is >= '0' and <= '9');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ public sealed class PhotoboothSettings
|
|||||||
public string? WatchFolder { get; set; }
|
public string? WatchFolder { get; set; }
|
||||||
public string? LastError { get; set; }
|
public string? LastError { get; set; }
|
||||||
public string? LastErrorAt { get; set; }
|
public string? LastErrorAt { get; set; }
|
||||||
|
public int MaxConcurrentUploads { get; set; } = 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
@@ -17,6 +18,7 @@ public sealed class UploadService
|
|||||||
private readonly ConcurrentDictionary<string, byte> _pending = new(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, byte> _pending = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private string _userAgent = "FotospielPhotoboothUploader";
|
private string _userAgent = "FotospielPhotoboothUploader";
|
||||||
private CancellationTokenSource? _cts;
|
private CancellationTokenSource? _cts;
|
||||||
|
private readonly List<Task> _workers = new();
|
||||||
|
|
||||||
public void Configure(string userAgent)
|
public void Configure(string userAgent)
|
||||||
{
|
{
|
||||||
@@ -36,7 +38,11 @@ public sealed class UploadService
|
|||||||
Stop();
|
Stop();
|
||||||
|
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
_ = Task.Run(() => WorkerAsync(settings, onQueued, onUploading, onSuccess, onFailure, _cts.Token));
|
var workerCount = GetWorkerCount(settings);
|
||||||
|
for (var i = 0; i < workerCount; i++)
|
||||||
|
{
|
||||||
|
_workers.Add(Task.Run(() => WorkerAsync(settings, onQueued, onUploading, onSuccess, onFailure, _cts.Token)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
@@ -44,6 +50,7 @@ public sealed class UploadService
|
|||||||
_cts?.Cancel();
|
_cts?.Cancel();
|
||||||
_cts = null;
|
_cts = null;
|
||||||
_pending.Clear();
|
_pending.Clear();
|
||||||
|
_workers.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Enqueue(string path, Action<string> onQueued)
|
public void Enqueue(string path, Action<string> onQueued)
|
||||||
@@ -171,4 +178,15 @@ public sealed class UploadService
|
|||||||
_ => "image/jpeg",
|
_ => "image/jpeg",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int GetWorkerCount(PhotoboothSettings settings)
|
||||||
|
{
|
||||||
|
var count = settings.MaxConcurrentUploads;
|
||||||
|
if (count < 1)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count > 5 ? 5 : count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user