using System.Collections.Concurrent; using System.Net.Http.Headers; using System.Threading.Channels; using PhotoboothUploader.Models; namespace PhotoboothUploader.Services; public sealed class UploadService { private readonly Channel _queue = Channel.CreateUnbounded(); private readonly ConcurrentDictionary _pending = new(StringComparer.OrdinalIgnoreCase); private CancellationTokenSource? _cts; public void Start(PhotoboothSettings settings, Action setStatus) { Stop(); _cts = new CancellationTokenSource(); _ = Task.Run(() => WorkerAsync(settings, setStatus, _cts.Token)); } public void Stop() { _cts?.Cancel(); _cts = null; _pending.Clear(); } public void Enqueue(string path) { if (!_pending.TryAdd(path, 0)) { return; } _queue.Writer.TryWrite(path); } private async Task WorkerAsync(PhotoboothSettings settings, Action setStatus, CancellationToken token) { if (string.IsNullOrWhiteSpace(settings.UploadUrl)) { return; } using var client = new HttpClient(); while (await _queue.Reader.WaitToReadAsync(token)) { while (_queue.Reader.TryRead(out var path)) { try { await WaitForFileReadyAsync(path, token); await UploadAsync(client, settings, path, token); setStatus($"Hochgeladen: {Path.GetFileName(path)}"); } catch (OperationCanceledException) { return; } catch { setStatus($"Upload fehlgeschlagen: {Path.GetFileName(path)}"); } finally { _pending.TryRemove(path, out _); } } } } private static async Task WaitForFileReadyAsync(string path, CancellationToken token) { var lastSize = -1L; for (var attempts = 0; attempts < 10; attempts++) { token.ThrowIfCancellationRequested(); if (!File.Exists(path)) { await Task.Delay(500, token); continue; } var info = new FileInfo(path); var size = info.Length; if (size > 0 && size == lastSize) { return; } lastSize = size; await Task.Delay(700, token); } } private static async Task UploadAsync(HttpClient client, PhotoboothSettings settings, string path, CancellationToken token) { if (!File.Exists(path)) { return; } using var content = new MultipartFormDataContent(); if (!string.IsNullOrWhiteSpace(settings.Username)) { content.Add(new StringContent(settings.Username), "username"); } if (!string.IsNullOrWhiteSpace(settings.Password)) { content.Add(new StringContent(settings.Password), "password"); } if (!string.IsNullOrWhiteSpace(settings.ResponseFormat)) { content.Add(new StringContent(settings.ResponseFormat), "format"); } var stream = File.OpenRead(path); var fileContent = new StreamContent(stream); fileContent.Headers.ContentType = new MediaTypeHeaderValue(ResolveContentType(path)); content.Add(fileContent, "media", Path.GetFileName(path)); var response = await client.PostAsync(settings.UploadUrl, content, token); response.EnsureSuccessStatusCode(); } private static string ResolveContentType(string path) { return Path.GetExtension(path)?.ToLowerInvariant() switch { ".png" => "image/png", ".webp" => "image/webp", _ => "image/jpeg", }; } }