Add photobooth connect codes and uploader pipeline
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
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<string> _queue = Channel.CreateUnbounded<string>();
|
||||
private readonly ConcurrentDictionary<string, byte> _pending = new(StringComparer.OrdinalIgnoreCase);
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
public void Start(PhotoboothSettings settings, Action<string> 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<string> 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",
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user