Add filters, throttling, and connection test
This commit is contained in:
@@ -65,6 +65,16 @@
|
||||
<TextBox x:Name="BaseUrlBox" Watermark="https://fotospiel.app" />
|
||||
<TextBlock Text="Max. parallele Uploads" />
|
||||
<TextBox x:Name="MaxUploadsBox" Watermark="2" />
|
||||
<TextBlock Text="Upload-Tempo" />
|
||||
<ComboBox x:Name="UploadTempoBox" SelectedIndex="1">
|
||||
<ComboBoxItem Content="Schnell (ohne Pause)" />
|
||||
<ComboBoxItem Content="Normal" />
|
||||
<ComboBoxItem Content="Sanft (schont Netzwerk)" />
|
||||
</ComboBox>
|
||||
<TextBlock Text="Nur diese Dateien (optional)" />
|
||||
<TextBox x:Name="IncludePatternsBox" Watermark="*.jpg;*.jpeg;*.png" />
|
||||
<TextBlock Text="Dateien ausschliessen (optional)" />
|
||||
<TextBox x:Name="ExcludePatternsBox" Watermark="*_preview*;*.tmp" />
|
||||
<TextBlock Text="Manuelle Zugangsdaten (optional)" FontWeight="SemiBold" Margin="0,8,0,0" />
|
||||
<TextBlock Text="Diese Felder ueberschreiben den Verbindungscode." Classes="subtitle" TextWrapping="Wrap" />
|
||||
<TextBlock Text="Upload-URL" />
|
||||
@@ -73,6 +83,7 @@
|
||||
<TextBox x:Name="ManualUsernameBox" />
|
||||
<TextBlock Text="Passwort" />
|
||||
<TextBox x:Name="ManualPasswordBox" PasswordChar="•" />
|
||||
<Button x:Name="TestConnectionButton" Content="Verbindung testen" Click="TestConnectionButton_Click" Classes="secondary" />
|
||||
<Button x:Name="SaveAdvancedButton" Content="Speichern" Click="SaveAdvancedButton_Click" Classes="primary" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -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<string>();
|
||||
_settings.UploadedFiles ??= new Dictionary<string, string>(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<string> SplitPatterns(string? input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
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<char> text, ReadOnlySpan<char> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> PendingUploads { get; set; } = new();
|
||||
public Dictionary<string, string> 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; }
|
||||
}
|
||||
|
||||
@@ -108,6 +108,10 @@ public sealed class UploadService
|
||||
finally
|
||||
{
|
||||
_pending.TryRemove(path, out _);
|
||||
if (settings.UploadDelayMs > 0)
|
||||
{
|
||||
await Task.Delay(settings.UploadDelayMs, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user