Extend uploader profiles, filters, and diagnostics
This commit is contained in:
@@ -61,6 +61,13 @@
|
|||||||
<Border x:Name="AdvancedPanel" Padding="12" Classes="card accent" IsVisible="False">
|
<Border x:Name="AdvancedPanel" Padding="12" Classes="card accent" IsVisible="False">
|
||||||
<StackPanel Spacing="6">
|
<StackPanel Spacing="6">
|
||||||
<TextBlock Text="Erweiterte Einstellungen" FontWeight="SemiBold" />
|
<TextBlock Text="Erweiterte Einstellungen" FontWeight="SemiBold" />
|
||||||
|
<ToggleSwitch x:Name="SettingsUnlockToggle" Content="Einstellungen entsperren" Checked="SettingsUnlockToggle_Changed" Unchecked="SettingsUnlockToggle_Changed" />
|
||||||
|
<TextBlock Text="Profile" />
|
||||||
|
<ComboBox x:Name="ProfilesBox" />
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Button x:Name="LoadProfileButton" Content="Profil laden" Click="LoadProfileButton_Click" Classes="secondary" />
|
||||||
|
<Button x:Name="SaveProfileButton" Content="Profil speichern" Click="SaveProfileButton_Click" Classes="secondary" />
|
||||||
|
</StackPanel>
|
||||||
<TextBlock Text="Basis-URL" />
|
<TextBlock Text="Basis-URL" />
|
||||||
<TextBox x:Name="BaseUrlBox" Watermark="https://fotospiel.app" />
|
<TextBox x:Name="BaseUrlBox" Watermark="https://fotospiel.app" />
|
||||||
<TextBlock Text="Max. parallele Uploads" />
|
<TextBlock Text="Max. parallele Uploads" />
|
||||||
@@ -75,6 +82,12 @@
|
|||||||
<TextBox x:Name="IncludePatternsBox" Watermark="*.jpg;*.jpeg;*.png" />
|
<TextBox x:Name="IncludePatternsBox" Watermark="*.jpg;*.jpeg;*.png" />
|
||||||
<TextBlock Text="Dateien ausschliessen (optional)" />
|
<TextBlock Text="Dateien ausschliessen (optional)" />
|
||||||
<TextBox x:Name="ExcludePatternsBox" Watermark="*_preview*;*.tmp" />
|
<TextBox x:Name="ExcludePatternsBox" Watermark="*_preview*;*.tmp" />
|
||||||
|
<TextBlock Text="Antwort-Format (optional)" />
|
||||||
|
<ComboBox x:Name="ResponseFormatBox" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Content="Auto" />
|
||||||
|
<ComboBoxItem Content="JSON" />
|
||||||
|
<ComboBoxItem Content="XML" />
|
||||||
|
</ComboBox>
|
||||||
<TextBlock Text="Manuelle Zugangsdaten (optional)" FontWeight="SemiBold" Margin="0,8,0,0" />
|
<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="Diese Felder ueberschreiben den Verbindungscode." Classes="subtitle" TextWrapping="Wrap" />
|
||||||
<TextBlock Text="Upload-URL" />
|
<TextBlock Text="Upload-URL" />
|
||||||
@@ -106,9 +119,12 @@
|
|||||||
<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="ConnectExpiryText" Text="Verbindungscode: —" TextWrapping="Wrap" />
|
||||||
<TextBlock x:Name="FolderHealthText" Text="Ordner: —" TextWrapping="Wrap" />
|
<TextBlock x:Name="FolderHealthText" Text="Ordner: —" TextWrapping="Wrap" />
|
||||||
<TextBlock x:Name="DiskFreeText" Text="Freier Speicher: —" TextWrapping="Wrap" />
|
<TextBlock x:Name="DiskFreeText" Text="Freier Speicher: —" TextWrapping="Wrap" />
|
||||||
|
<TextBlock x:Name="LastSeenText" Text="Letzte Datei: —" TextWrapping="Wrap" />
|
||||||
<TextBlock x:Name="LastErrorText" Text="Letzter Fehler: —" TextWrapping="Wrap" />
|
<TextBlock x:Name="LastErrorText" Text="Letzter Fehler: —" TextWrapping="Wrap" />
|
||||||
|
<Button x:Name="LogCopyButton" Content="Log kopieren" Click="LogCopyButton_Click" Classes="secondary" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
@@ -128,7 +144,10 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
<Button x:Name="RetryFailedButton" Content="Fehlgeschlagene erneut senden" Click="RetryFailedButton_Click" IsEnabled="False" Classes="secondary" />
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Button x:Name="RetryFailedButton" Content="Fehlgeschlagene erneut senden" Click="RetryFailedButton_Click" IsEnabled="False" Classes="secondary" />
|
||||||
|
<Button x:Name="ClearFailedButton" Content="Fehlerliste leeren" Click="ClearFailedButton_Click" IsEnabled="False" Classes="secondary" />
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public partial class MainWindow : Window
|
|||||||
private DateTimeOffset? _lastSuccessAt;
|
private DateTimeOffset? _lastSuccessAt;
|
||||||
private bool _advancedVisible;
|
private bool _advancedVisible;
|
||||||
private readonly DispatcherTimer _liveTimer = new();
|
private readonly DispatcherTimer _liveTimer = new();
|
||||||
|
private readonly List<string> _logBuffer = new();
|
||||||
|
|
||||||
public ObservableCollection<UploadItem> RecentUploads { get; } = new();
|
public ObservableCollection<UploadItem> RecentUploads { get; } = new();
|
||||||
|
|
||||||
@@ -130,12 +131,15 @@ public partial class MainWindow : Window
|
|||||||
_settings.Password = response.Data.Password;
|
_settings.Password = response.Data.Password;
|
||||||
_settings.ResponseFormat = response.Data.ResponseFormat;
|
_settings.ResponseFormat = response.Data.ResponseFormat;
|
||||||
_settings.EventName = response.Data.EventName;
|
_settings.EventName = response.Data.EventName;
|
||||||
|
_settings.ConnectExpiresAt = response.Data.ExpiresAt;
|
||||||
_settingsStore.Save(_settings);
|
_settingsStore.Save(_settings);
|
||||||
|
|
||||||
StatusText.Text = "Verbunden. Upload bereit.";
|
StatusText.Text = "Verbunden. Upload bereit.";
|
||||||
|
AppendLog("Verbunden mit Event.");
|
||||||
PickFolderButton.IsEnabled = true;
|
PickFolderButton.IsEnabled = true;
|
||||||
TestUploadButton.IsEnabled = true;
|
TestUploadButton.IsEnabled = true;
|
||||||
ReconnectButton.IsEnabled = true;
|
ReconnectButton.IsEnabled = true;
|
||||||
|
UpdateAdvancedLockState();
|
||||||
UpdateDiagnostics();
|
UpdateDiagnostics();
|
||||||
StartUploadPipelineIfReady();
|
StartUploadPipelineIfReady();
|
||||||
}
|
}
|
||||||
@@ -177,9 +181,11 @@ public partial class MainWindow : Window
|
|||||||
UploadTempoBox.SelectedIndex = ResolveUploadTempoIndex(_settings.UploadDelayMs);
|
UploadTempoBox.SelectedIndex = ResolveUploadTempoIndex(_settings.UploadDelayMs);
|
||||||
IncludePatternsBox.Text = _settings.IncludePatterns ?? string.Empty;
|
IncludePatternsBox.Text = _settings.IncludePatterns ?? string.Empty;
|
||||||
ExcludePatternsBox.Text = _settings.ExcludePatterns ?? string.Empty;
|
ExcludePatternsBox.Text = _settings.ExcludePatterns ?? string.Empty;
|
||||||
|
ResponseFormatBox.SelectedIndex = ResolveResponseFormatIndex(_settings.ResponseFormat);
|
||||||
ManualUploadUrlBox.Text = _settings.UploadUrl ?? string.Empty;
|
ManualUploadUrlBox.Text = _settings.UploadUrl ?? string.Empty;
|
||||||
ManualUsernameBox.Text = _settings.Username ?? string.Empty;
|
ManualUsernameBox.Text = _settings.Username ?? string.Empty;
|
||||||
ManualPasswordBox.Text = string.Empty;
|
ManualPasswordBox.Text = string.Empty;
|
||||||
|
SettingsUnlockToggle.IsChecked = false;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
|
if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
|
||||||
{
|
{
|
||||||
@@ -194,6 +200,8 @@ public partial class MainWindow : Window
|
|||||||
UpdateFolderHealth();
|
UpdateFolderHealth();
|
||||||
UpdateDiagnostics();
|
UpdateDiagnostics();
|
||||||
UpdateSteps();
|
UpdateSteps();
|
||||||
|
RefreshProfiles();
|
||||||
|
UpdateAdvancedLockState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnWindowOpened(object? sender, EventArgs e)
|
private void OnWindowOpened(object? sender, EventArgs e)
|
||||||
@@ -263,6 +271,7 @@ public partial class MainWindow : Window
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RecordLastSeen(e.FullPath);
|
||||||
_uploadService.Enqueue(e.FullPath, OnQueued);
|
_uploadService.Enqueue(e.FullPath, OnQueued);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +287,7 @@ public partial class MainWindow : Window
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RecordLastSeen(e.FullPath);
|
||||||
_uploadService.Enqueue(e.FullPath, OnQueued);
|
_uploadService.Enqueue(e.FullPath, OnQueued);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +309,7 @@ public partial class MainWindow : Window
|
|||||||
UpdateUpload(path, UploadStatus.Queued);
|
UpdateUpload(path, UploadStatus.Queued);
|
||||||
AddPendingUpload(path);
|
AddPendingUpload(path);
|
||||||
UpdateStatusIfAllowed($"Wartet: {Path.GetFileName(path)}", false);
|
UpdateStatusIfAllowed($"Wartet: {Path.GetFileName(path)}", false);
|
||||||
|
AppendLog($"Wartet: {Path.GetFileName(path)}");
|
||||||
UpdateCountersText();
|
UpdateCountersText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,6 +331,7 @@ public partial class MainWindow : Window
|
|||||||
RemovePendingUpload(path);
|
RemovePendingUpload(path);
|
||||||
MarkUploaded(path);
|
MarkUploaded(path);
|
||||||
UpdateStatusIfAllowed($"Hochgeladen: {Path.GetFileName(path)}", false);
|
UpdateStatusIfAllowed($"Hochgeladen: {Path.GetFileName(path)}", false);
|
||||||
|
AppendLog($"Hochgeladen: {Path.GetFileName(path)}");
|
||||||
UpdateCountersText();
|
UpdateCountersText();
|
||||||
UpdateLiveStatus();
|
UpdateLiveStatus();
|
||||||
}
|
}
|
||||||
@@ -333,6 +345,7 @@ public partial class MainWindow : Window
|
|||||||
RemovePendingUpload(path);
|
RemovePendingUpload(path);
|
||||||
UpdateStatusIfAllowed($"Upload fehlgeschlagen: {Path.GetFileName(path)}", true);
|
UpdateStatusIfAllowed($"Upload fehlgeschlagen: {Path.GetFileName(path)}", true);
|
||||||
SetLastError($"{Path.GetFileName(path)} – {message}");
|
SetLastError($"{Path.GetFileName(path)} – {message}");
|
||||||
|
AppendLog($"Fehlgeschlagen: {Path.GetFileName(path)} – {message}");
|
||||||
UpdateRetryButton();
|
UpdateRetryButton();
|
||||||
UpdateCountersText();
|
UpdateCountersText();
|
||||||
}
|
}
|
||||||
@@ -379,6 +392,7 @@ public partial class MainWindow : Window
|
|||||||
private void UpdateRetryButton()
|
private void UpdateRetryButton()
|
||||||
{
|
{
|
||||||
RetryFailedButton.IsEnabled = _failedPaths.Count > 0;
|
RetryFailedButton.IsEnabled = _failedPaths.Count > 0;
|
||||||
|
ClearFailedButton.IsEnabled = _failedPaths.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RetryFailedButton_Click(object? sender, RoutedEventArgs e)
|
private void RetryFailedButton_Click(object? sender, RoutedEventArgs e)
|
||||||
@@ -401,6 +415,21 @@ public partial class MainWindow : Window
|
|||||||
UpdateCountersText();
|
UpdateCountersText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearFailedButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_failedPaths.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleared = _failedPaths.Count;
|
||||||
|
_failedPaths.Clear();
|
||||||
|
Interlocked.Add(ref _failedCount, -cleared);
|
||||||
|
UpdateRetryButton();
|
||||||
|
UpdateCountersText();
|
||||||
|
UpdateStatusIfAllowed("Fehlerliste geleert.", false);
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateSteps()
|
private void UpdateSteps()
|
||||||
{
|
{
|
||||||
var hasCode = !string.IsNullOrWhiteSpace(_settings.UploadUrl);
|
var hasCode = !string.IsNullOrWhiteSpace(_settings.UploadUrl);
|
||||||
@@ -419,6 +448,11 @@ public partial class MainWindow : Window
|
|||||||
ReconnectButton.IsEnabled = enabled;
|
ReconnectButton.IsEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SettingsUnlockToggle_Changed(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
UpdateAdvancedLockState();
|
||||||
|
}
|
||||||
|
|
||||||
private void RestorePendingUploads()
|
private void RestorePendingUploads()
|
||||||
{
|
{
|
||||||
EnsureSettingsCollections();
|
EnsureSettingsCollections();
|
||||||
@@ -553,6 +587,7 @@ public partial class MainWindow : Window
|
|||||||
_settings.UploadDelayMs = ResolveUploadDelay(UploadTempoBox.SelectedIndex);
|
_settings.UploadDelayMs = ResolveUploadDelay(UploadTempoBox.SelectedIndex);
|
||||||
_settings.IncludePatterns = NormalizePatternInput(IncludePatternsBox.Text);
|
_settings.IncludePatterns = NormalizePatternInput(IncludePatternsBox.Text);
|
||||||
_settings.ExcludePatterns = NormalizePatternInput(ExcludePatternsBox.Text);
|
_settings.ExcludePatterns = NormalizePatternInput(ExcludePatternsBox.Text);
|
||||||
|
_settings.ResponseFormat = ResolveResponseFormat(ResponseFormatBox.SelectedIndex);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(manualUploadUrl))
|
if (!string.IsNullOrWhiteSpace(manualUploadUrl))
|
||||||
{
|
{
|
||||||
@@ -577,6 +612,7 @@ public partial class MainWindow : Window
|
|||||||
UpdateFolderHealth();
|
UpdateFolderHealth();
|
||||||
RestartUploadPipeline();
|
RestartUploadPipeline();
|
||||||
UpdateSteps();
|
UpdateSteps();
|
||||||
|
UpdateAdvancedLockState();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
|
if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
|
||||||
{
|
{
|
||||||
@@ -678,6 +714,7 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
FolderHealthText.Text = "Ordner: —";
|
FolderHealthText.Text = "Ordner: —";
|
||||||
DiskFreeText.Text = "Freier Speicher: —";
|
DiskFreeText.Text = "Freier Speicher: —";
|
||||||
|
LastSeenText.Text = "Letzte Datei: —";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,13 +722,14 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
FolderHealthText.Text = "Ordner: fehlt";
|
FolderHealthText.Text = "Ordner: fehlt";
|
||||||
DiskFreeText.Text = "Freier Speicher: —";
|
DiskFreeText.Text = "Freier Speicher: —";
|
||||||
|
LastSeenText.Text = "Letzte Datei: —";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = Directory.EnumerateFileSystemEntries(folder).FirstOrDefault();
|
_ = Directory.EnumerateFileSystemEntries(folder).FirstOrDefault();
|
||||||
FolderHealthText.Text = "Ordner: OK";
|
FolderHealthText.Text = CanWriteToFolder(folder) ? "Ordner: OK (schreibbar)" : "Ordner: OK (nur lesen)";
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
@@ -702,7 +740,8 @@ public partial class MainWindow : Window
|
|||||||
FolderHealthText.Text = "Ordner: Fehler";
|
FolderHealthText.Text = "Ordner: Fehler";
|
||||||
}
|
}
|
||||||
|
|
||||||
DiskFreeText.Text = $"Freier Speicher: {FormatDiskFree(folder)}";
|
DiskFreeText.Text = FormatDiskFree(folder);
|
||||||
|
LastSeenText.Text = FormatLastSeen();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateLiveStatus()
|
private void UpdateLiveStatus()
|
||||||
@@ -712,6 +751,7 @@ public partial class MainWindow : Window
|
|||||||
if (_lastSuccessAt is null)
|
if (_lastSuccessAt is null)
|
||||||
{
|
{
|
||||||
LiveStatusText.Text = "Live: —";
|
LiveStatusText.Text = "Live: —";
|
||||||
|
UpdateDiagnostics();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,6 +759,7 @@ public partial class MainWindow : Window
|
|||||||
var isLive = age <= TimeSpan.FromMinutes(5);
|
var isLive = age <= TimeSpan.FromMinutes(5);
|
||||||
var label = isLive ? "Live: Ja" : "Live: Nein";
|
var label = isLive ? "Live: Ja" : "Live: Nein";
|
||||||
LiveStatusText.Text = $"{label} (letzter Upload {FormatRelativeAge(age)})";
|
LiveStatusText.Text = $"{label} (letzter Upload {FormatRelativeAge(age)})";
|
||||||
|
UpdateDiagnostics();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,17 +788,18 @@ public partial class MainWindow : Window
|
|||||||
var root = Path.GetPathRoot(folder);
|
var root = Path.GetPathRoot(folder);
|
||||||
if (string.IsNullOrWhiteSpace(root))
|
if (string.IsNullOrWhiteSpace(root))
|
||||||
{
|
{
|
||||||
return "—";
|
return "Freier Speicher: —";
|
||||||
}
|
}
|
||||||
|
|
||||||
var drive = new DriveInfo(root);
|
var drive = new DriveInfo(root);
|
||||||
if (!drive.IsReady)
|
if (!drive.IsReady)
|
||||||
{
|
{
|
||||||
return "—";
|
return "Freier Speicher: —";
|
||||||
}
|
}
|
||||||
|
|
||||||
var freeGb = drive.AvailableFreeSpace / (1024d * 1024d * 1024d);
|
var freeGb = drive.AvailableFreeSpace / (1024d * 1024d * 1024d);
|
||||||
return $"{freeGb:0.0} GB";
|
var label = freeGb < 5 ? "niedrig" : "ok";
|
||||||
|
return $"Freier Speicher: {freeGb:0.0} GB ({label})";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ResolveUploadUrl(string? uploadUrl)
|
private string? ResolveUploadUrl(string? uploadUrl)
|
||||||
@@ -816,6 +858,7 @@ public partial class MainWindow : Window
|
|||||||
EventNameText.Text = $"Event: {_settings.EventName ?? "—"}";
|
EventNameText.Text = $"Event: {_settings.EventName ?? "—"}";
|
||||||
BaseUrlText.Text = $"Basis-URL: {_settings.BaseUrl ?? "—"}";
|
BaseUrlText.Text = $"Basis-URL: {_settings.BaseUrl ?? "—"}";
|
||||||
VersionText.Text = $"App-Version: {GetAppVersion()}";
|
VersionText.Text = $"App-Version: {GetAppVersion()}";
|
||||||
|
ConnectExpiryText.Text = FormatConnectExpiry();
|
||||||
LastErrorText.Text = $"Letzter Fehler: {FormatLastError()}";
|
LastErrorText.Text = $"Letzter Fehler: {FormatLastError()}";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -866,6 +909,71 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
_settings.PendingUploads ??= new List<string>();
|
_settings.PendingUploads ??= new List<string>();
|
||||||
_settings.UploadedFiles ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
_settings.UploadedFiles ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
_settings.Profiles ??= new List<PhotoboothProfile>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecordLastSeen(string path)
|
||||||
|
{
|
||||||
|
_settings.LastSeenFile = Path.GetFileName(path);
|
||||||
|
_settings.LastSeenAt = DateTimeOffset.Now.ToString("O");
|
||||||
|
_settingsStore.Save(_settings);
|
||||||
|
UpdateFolderHealth();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanWriteToFolder(string folder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var testFile = Path.Combine(folder, $".fotospiel-write-test-{Guid.NewGuid():N}.tmp");
|
||||||
|
File.WriteAllText(testFile, "ok");
|
||||||
|
File.Delete(testFile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatLastSeen()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_settings.LastSeenAt) || string.IsNullOrWhiteSpace(_settings.LastSeenFile))
|
||||||
|
{
|
||||||
|
return "Letzte Datei: —";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTimeOffset.TryParse(_settings.LastSeenAt, out var timestamp))
|
||||||
|
{
|
||||||
|
return $"Letzte Datei: {_settings.LastSeenFile} ({timestamp:HH:mm})";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"Letzte Datei: {_settings.LastSeenFile}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatConnectExpiry()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_settings.ConnectExpiresAt))
|
||||||
|
{
|
||||||
|
return "Verbindungscode: —";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DateTimeOffset.TryParse(_settings.ConnectExpiresAt, out var expiry))
|
||||||
|
{
|
||||||
|
return "Verbindungscode: —";
|
||||||
|
}
|
||||||
|
|
||||||
|
var remaining = expiry - DateTimeOffset.Now;
|
||||||
|
if (remaining <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
return "Verbindungscode: abgelaufen";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining.TotalHours >= 1)
|
||||||
|
{
|
||||||
|
return $"Verbindungscode: {Math.Ceiling(remaining.TotalHours)} h gueltig";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"Verbindungscode: {Math.Ceiling(remaining.TotalMinutes)} min gueltig";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ResolveTestUrl()
|
private string? ResolveTestUrl()
|
||||||
@@ -926,6 +1034,31 @@ public partial class MainWindow : Window
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? ResolveResponseFormat(int selectedIndex)
|
||||||
|
{
|
||||||
|
return selectedIndex switch
|
||||||
|
{
|
||||||
|
1 => "json",
|
||||||
|
2 => "xml",
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ResolveResponseFormatIndex(string? format)
|
||||||
|
{
|
||||||
|
if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(format, "xml", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private static string NormalizePatternInput(string? input)
|
private static string NormalizePatternInput(string? input)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
@@ -1022,4 +1155,138 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
return patternIndex == pattern.Length;
|
return patternIndex == pattern.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateAdvancedLockState()
|
||||||
|
{
|
||||||
|
var connected = !string.IsNullOrWhiteSpace(_settings.UploadUrl);
|
||||||
|
var unlocked = SettingsUnlockToggle?.IsChecked ?? false;
|
||||||
|
var enabled = !connected || unlocked;
|
||||||
|
|
||||||
|
BaseUrlBox.IsEnabled = enabled;
|
||||||
|
MaxUploadsBox.IsEnabled = enabled;
|
||||||
|
UploadTempoBox.IsEnabled = enabled;
|
||||||
|
IncludePatternsBox.IsEnabled = enabled;
|
||||||
|
ExcludePatternsBox.IsEnabled = enabled;
|
||||||
|
ResponseFormatBox.IsEnabled = enabled;
|
||||||
|
ManualUploadUrlBox.IsEnabled = enabled;
|
||||||
|
ManualUsernameBox.IsEnabled = enabled;
|
||||||
|
ManualPasswordBox.IsEnabled = enabled;
|
||||||
|
TestConnectionButton.IsEnabled = enabled;
|
||||||
|
SaveAdvancedButton.IsEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshProfiles()
|
||||||
|
{
|
||||||
|
ProfilesBox.ItemsSource = _settings.Profiles.Select(profile => profile.DisplayName).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadProfileButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var index = ProfilesBox.SelectedIndex;
|
||||||
|
if (index < 0 || index >= _settings.Profiles.Count)
|
||||||
|
{
|
||||||
|
UpdateStatus("Bitte zuerst ein Profil auswaehlen.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile = _settings.Profiles[index];
|
||||||
|
_settings.BaseUrl = NormalizeBaseUrl(profile.BaseUrl) ?? _settings.BaseUrl;
|
||||||
|
_settings.UploadUrl = profile.UploadUrl;
|
||||||
|
_settings.Username = profile.Username;
|
||||||
|
_settings.Password = profile.Password;
|
||||||
|
_settings.ResponseFormat = profile.ResponseFormat;
|
||||||
|
_settings.WatchFolder = profile.WatchFolder;
|
||||||
|
_settings.IncludePatterns = profile.IncludePatterns;
|
||||||
|
_settings.ExcludePatterns = profile.ExcludePatterns;
|
||||||
|
_settings.MaxConcurrentUploads = profile.MaxConcurrentUploads > 0 ? profile.MaxConcurrentUploads : _settings.MaxConcurrentUploads;
|
||||||
|
_settings.UploadDelayMs = profile.UploadDelayMs;
|
||||||
|
_settingsStore.Save(_settings);
|
||||||
|
|
||||||
|
ApplySettings();
|
||||||
|
UpdateStatus("Profil geladen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveProfileButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var label = _settings.EventName ?? "Neues Profil";
|
||||||
|
var profile = new PhotoboothProfile
|
||||||
|
{
|
||||||
|
Label = label,
|
||||||
|
EventName = _settings.EventName,
|
||||||
|
BaseUrl = _settings.BaseUrl,
|
||||||
|
UploadUrl = _settings.UploadUrl,
|
||||||
|
Username = _settings.Username,
|
||||||
|
Password = _settings.Password,
|
||||||
|
ResponseFormat = _settings.ResponseFormat,
|
||||||
|
WatchFolder = _settings.WatchFolder,
|
||||||
|
IncludePatterns = _settings.IncludePatterns,
|
||||||
|
ExcludePatterns = _settings.ExcludePatterns,
|
||||||
|
MaxConcurrentUploads = _settings.MaxConcurrentUploads,
|
||||||
|
UploadDelayMs = _settings.UploadDelayMs,
|
||||||
|
};
|
||||||
|
|
||||||
|
_settings.Profiles.RemoveAll(existing => string.Equals(existing.DisplayName, profile.DisplayName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
_settings.Profiles.Insert(0, profile);
|
||||||
|
_settingsStore.Save(_settings);
|
||||||
|
RefreshProfiles();
|
||||||
|
ProfilesBox.SelectedIndex = 0;
|
||||||
|
UpdateStatus("Profil gespeichert.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void LogCopyButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var content = ReadLogForCopy();
|
||||||
|
if (string.IsNullOrWhiteSpace(content))
|
||||||
|
{
|
||||||
|
UpdateStatus("Log ist leer.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
|
||||||
|
if (clipboard is null)
|
||||||
|
{
|
||||||
|
UpdateStatus("Zwischenablage nicht verfuegbar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await clipboard.SetTextAsync(content);
|
||||||
|
UpdateStatus("Log kopiert.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendLog(string message)
|
||||||
|
{
|
||||||
|
var line = $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss} {message}";
|
||||||
|
_logBuffer.Add(line);
|
||||||
|
while (_logBuffer.Count > 200)
|
||||||
|
{
|
||||||
|
_logBuffer.RemoveAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.AppendAllLines(_settingsStore.LogPath, new[] { line });
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore file errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ReadLogForCopy()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(_settingsStore.LogPath))
|
||||||
|
{
|
||||||
|
var lines = File.ReadAllLines(_settingsStore.LogPath);
|
||||||
|
return string.Join(Environment.NewLine, lines.TakeLast(200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return _logBuffer.Count > 0 ? string.Join(Environment.NewLine, _logBuffer) : string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PhotoboothUploader.Models;
|
||||||
|
|
||||||
|
public sealed class PhotoboothProfile
|
||||||
|
{
|
||||||
|
public string? Label { get; set; }
|
||||||
|
public string? EventName { get; set; }
|
||||||
|
public string? BaseUrl { get; set; }
|
||||||
|
public string? UploadUrl { get; set; }
|
||||||
|
public string? Username { get; set; }
|
||||||
|
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 int MaxConcurrentUploads { get; set; } = 2;
|
||||||
|
public int UploadDelayMs { get; set; } = 500;
|
||||||
|
|
||||||
|
public string DisplayName
|
||||||
|
=> !string.IsNullOrWhiteSpace(Label)
|
||||||
|
? Label
|
||||||
|
: !string.IsNullOrWhiteSpace(EventName)
|
||||||
|
? EventName
|
||||||
|
: UploadUrl ?? BaseUrl ?? "Profil";
|
||||||
|
}
|
||||||
@@ -16,6 +16,10 @@ public sealed class PhotoboothSettings
|
|||||||
public string? ExcludePatterns { get; set; }
|
public string? ExcludePatterns { get; set; }
|
||||||
public List<string> PendingUploads { get; set; } = new();
|
public List<string> PendingUploads { get; set; } = new();
|
||||||
public Dictionary<string, string> UploadedFiles { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
public Dictionary<string, string> UploadedFiles { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
public List<PhotoboothProfile> Profiles { get; set; } = new();
|
||||||
|
public string? ConnectExpiresAt { get; set; }
|
||||||
|
public string? LastSeenFile { get; set; }
|
||||||
|
public string? LastSeenAt { 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;
|
public int MaxConcurrentUploads { get; set; } = 2;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public sealed class SettingsStore
|
|||||||
};
|
};
|
||||||
|
|
||||||
public string SettingsPath { get; }
|
public string SettingsPath { get; }
|
||||||
|
public string LogPath { get; }
|
||||||
|
|
||||||
public SettingsStore()
|
public SettingsStore()
|
||||||
{
|
{
|
||||||
@@ -24,6 +25,7 @@ public sealed class SettingsStore
|
|||||||
|
|
||||||
Directory.CreateDirectory(basePath);
|
Directory.CreateDirectory(basePath);
|
||||||
SettingsPath = Path.Combine(basePath, "settings.json");
|
SettingsPath = Path.Combine(basePath, "settings.json");
|
||||||
|
LogPath = Path.Combine(basePath, "uploader.log");
|
||||||
}
|
}
|
||||||
|
|
||||||
public PhotoboothSettings Load()
|
public PhotoboothSettings Load()
|
||||||
|
|||||||
Reference in New Issue
Block a user