diff --git a/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml b/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml
index a6c889b..a725c06 100644
--- a/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml
+++ b/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml
@@ -65,6 +65,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -73,6 +83,7 @@
+
diff --git a/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml.cs b/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml.cs
index 4a6efae..0c4097e 100644
--- a/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml.cs
+++ b/clients/photobooth-uploader/PhotoboothUploader/MainWindow.axaml.cs
@@ -6,6 +6,8 @@ using System.Collections.ObjectModel;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using System.Net.Http;
+using System.Net.Http.Headers;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
@@ -172,6 +174,9 @@ public partial class MainWindow : Window
BaseUrlBox.Text = _settings.BaseUrl ?? DefaultBaseUrl;
MaxUploadsBox.Text = _settings.MaxConcurrentUploads.ToString();
+ UploadTempoBox.SelectedIndex = ResolveUploadTempoIndex(_settings.UploadDelayMs);
+ IncludePatternsBox.Text = _settings.IncludePatterns ?? string.Empty;
+ ExcludePatternsBox.Text = _settings.ExcludePatterns ?? string.Empty;
ManualUploadUrlBox.Text = _settings.UploadUrl ?? string.Empty;
ManualUsernameBox.Text = _settings.Username ?? string.Empty;
ManualPasswordBox.Text = string.Empty;
@@ -446,6 +451,17 @@ public partial class MainWindow : Window
private bool ShouldSkipUpload(string path)
{
+ var fileName = Path.GetFileName(path);
+ if (!MatchesIncludePatterns(fileName))
+ {
+ return true;
+ }
+
+ if (MatchesExcludePatterns(fileName))
+ {
+ return true;
+ }
+
var signature = GetUploadSignature(path);
if (signature is null)
{
@@ -534,6 +550,9 @@ public partial class MainWindow : Window
_settings.BaseUrl = normalizedBaseUrl;
_settings.MaxConcurrentUploads = maxUploads;
+ _settings.UploadDelayMs = ResolveUploadDelay(UploadTempoBox.SelectedIndex);
+ _settings.IncludePatterns = NormalizePatternInput(IncludePatternsBox.Text);
+ _settings.ExcludePatterns = NormalizePatternInput(ExcludePatternsBox.Text);
if (!string.IsNullOrWhiteSpace(manualUploadUrl))
{
@@ -569,6 +588,40 @@ public partial class MainWindow : Window
UpdateStatus("Einstellungen gespeichert.");
}
+ private async void TestConnectionButton_Click(object? sender, RoutedEventArgs e)
+ {
+ var targetUrl = ResolveTestUrl();
+ if (targetUrl is null)
+ {
+ UpdateStatus("Bitte eine gültige Upload-URL oder Basis-URL speichern.");
+ return;
+ }
+
+ UpdateStatus("Verbindung wird getestet...");
+
+ try
+ {
+ using var client = new HttpClient
+ {
+ Timeout = TimeSpan.FromSeconds(8),
+ };
+ client.DefaultRequestHeaders.UserAgent.ParseAdd(_userAgent);
+ client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ using var request = new HttpRequestMessage(HttpMethod.Head, targetUrl);
+ using var response = await client.SendAsync(request);
+ var status = $"{(int)response.StatusCode} {response.ReasonPhrase}".Trim();
+ UpdateStatus($"Server erreichbar ({status}).");
+ }
+ catch (TaskCanceledException)
+ {
+ UpdateStatus("Verbindungstest: Zeitüberschreitung.");
+ }
+ catch (HttpRequestException)
+ {
+ UpdateStatus("Verbindungstest: Netzwerkfehler.");
+ }
+ }
+
private async void TestUploadButton_Click(object? sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(_settings.UploadUrl))
@@ -814,4 +867,159 @@ public partial class MainWindow : Window
_settings.PendingUploads ??= new List();
_settings.UploadedFiles ??= new Dictionary(StringComparer.OrdinalIgnoreCase);
}
+
+ private string? ResolveTestUrl()
+ {
+ var manual = NormalizeUrl(ManualUploadUrlBox.Text);
+ if (!string.IsNullOrWhiteSpace(manual))
+ {
+ return manual;
+ }
+
+ if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
+ {
+ return _settings.UploadUrl;
+ }
+
+ var normalizedBase = NormalizeBaseUrl(BaseUrlBox.Text);
+ return normalizedBase;
+ }
+
+ private static string? NormalizeUrl(string? value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return null;
+ }
+
+ var trimmed = value.Trim();
+ if (Uri.TryCreate(trimmed, UriKind.Absolute, out _))
+ {
+ return trimmed;
+ }
+
+ return null;
+ }
+
+ private static int ResolveUploadDelay(int selectedIndex)
+ {
+ return selectedIndex switch
+ {
+ 0 => 0,
+ 2 => 1500,
+ _ => 500,
+ };
+ }
+
+ private static int ResolveUploadTempoIndex(int delay)
+ {
+ if (delay <= 0)
+ {
+ return 0;
+ }
+
+ if (delay >= 1500)
+ {
+ return 2;
+ }
+
+ return 1;
+ }
+
+ private static string NormalizePatternInput(string? input)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ return string.Empty;
+ }
+
+ var parts = SplitPatterns(input);
+ return string.Join(';', parts);
+ }
+
+ private bool MatchesIncludePatterns(string fileName)
+ {
+ var patterns = SplitPatterns(_settings.IncludePatterns);
+ if (patterns.Count == 0)
+ {
+ return true;
+ }
+
+ return patterns.Any(pattern => IsPatternMatch(fileName, pattern));
+ }
+
+ private bool MatchesExcludePatterns(string fileName)
+ {
+ var patterns = SplitPatterns(_settings.ExcludePatterns);
+ if (patterns.Count == 0)
+ {
+ return false;
+ }
+
+ return patterns.Any(pattern => IsPatternMatch(fileName, pattern));
+ }
+
+ private static List SplitPatterns(string? input)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ return new List();
+ }
+
+ return input
+ .Split(new[] { ';', ',', '\n' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
+ .Where(pattern => pattern.Length > 0)
+ .ToList();
+ }
+
+ private static bool IsPatternMatch(string fileName, string pattern)
+ {
+ var span = fileName.AsSpan();
+ var token = pattern.AsSpan();
+ return IsPatternMatch(span, token);
+ }
+
+ private static bool IsPatternMatch(ReadOnlySpan text, ReadOnlySpan pattern)
+ {
+ var textIndex = 0;
+ var patternIndex = 0;
+ var starIndex = -1;
+ var matchIndex = 0;
+
+ while (textIndex < text.Length)
+ {
+ if (patternIndex < pattern.Length
+ && (pattern[patternIndex] == '?' || char.ToLowerInvariant(pattern[patternIndex]) == char.ToLowerInvariant(text[textIndex])))
+ {
+ textIndex++;
+ patternIndex++;
+ continue;
+ }
+
+ if (patternIndex < pattern.Length && pattern[patternIndex] == '*')
+ {
+ starIndex = patternIndex;
+ matchIndex = textIndex;
+ patternIndex++;
+ continue;
+ }
+
+ if (starIndex != -1)
+ {
+ patternIndex = starIndex + 1;
+ matchIndex++;
+ textIndex = matchIndex;
+ continue;
+ }
+
+ return false;
+ }
+
+ while (patternIndex < pattern.Length && pattern[patternIndex] == '*')
+ {
+ patternIndex++;
+ }
+
+ return patternIndex == pattern.Length;
+ }
}
diff --git a/clients/photobooth-uploader/PhotoboothUploader/Models/PhotoboothSettings.cs b/clients/photobooth-uploader/PhotoboothUploader/Models/PhotoboothSettings.cs
index d573d36..ab0e370 100644
--- a/clients/photobooth-uploader/PhotoboothUploader/Models/PhotoboothSettings.cs
+++ b/clients/photobooth-uploader/PhotoboothUploader/Models/PhotoboothSettings.cs
@@ -12,11 +12,14 @@ public sealed class PhotoboothSettings
public string? Password { get; set; }
public string? ResponseFormat { get; set; }
public string? WatchFolder { get; set; }
+ public string? IncludePatterns { get; set; }
+ public string? ExcludePatterns { get; set; }
public List PendingUploads { get; set; } = new();
public Dictionary UploadedFiles { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public string? LastError { get; set; }
public string? LastErrorAt { get; set; }
public int MaxConcurrentUploads { get; set; } = 2;
+ public int UploadDelayMs { get; set; } = 500;
public double WindowWidth { get; set; }
public double WindowHeight { get; set; }
}
diff --git a/clients/photobooth-uploader/PhotoboothUploader/Services/UploadService.cs b/clients/photobooth-uploader/PhotoboothUploader/Services/UploadService.cs
index 3076c02..bcd65fd 100644
--- a/clients/photobooth-uploader/PhotoboothUploader/Services/UploadService.cs
+++ b/clients/photobooth-uploader/PhotoboothUploader/Services/UploadService.cs
@@ -108,6 +108,10 @@ public sealed class UploadService
finally
{
_pending.TryRemove(path, out _);
+ if (settings.UploadDelayMs > 0)
+ {
+ await Task.Delay(settings.UploadDelayMs, token);
+ }
}
}
}