Refactor ILibraryService and LibraryService to support new metadata handling features, including remote image management and enhanced label summaries. Update LabelSummary to include ImagePath for better visual representation. Revise MetadataMatchViewModel and MetadataScanViewModel to accommodate new image loading logic. Enhance README.md to document these updates and new functionalities.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PLib.Application.Library;
|
||||
|
||||
namespace PLib.Desktop.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the pictures a metadata source pointed at, behind whatever is already on screen.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Nothing waits on this. A candidate is shown the moment the source answers, and its cover
|
||||
/// appears when it appears — the alternative, fetching first, put a stranger's picture host on
|
||||
/// the critical path of showing a result, and one that stalled froze the whole run.
|
||||
///
|
||||
/// Concurrency is capped because a run can produce results faster than pictures download, and
|
||||
/// an unbounded fan-out would open a connection per candidate to the same host.
|
||||
/// </remarks>
|
||||
public sealed class RemoteImageLoader(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
ILogger<RemoteImageLoader> logger) : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _slots = new(4);
|
||||
private readonly Subject<string> _problems = new();
|
||||
|
||||
/// <summary>
|
||||
/// Reasons pictures are not arriving, for whatever page is on screen to show.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An empty square tells the user nothing — it looks the same whether the source has no
|
||||
/// picture or the whole host is unreachable. The cache raises one of these only once it
|
||||
/// has given up on a host, so this is a handful of lines, not one per candidate.
|
||||
/// </remarks>
|
||||
public IObservable<string> Problems => _problems.AsObservable();
|
||||
|
||||
public async Task<string?> LoadAsync(string? imageUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(imageUrl))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _slots.WaitAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
await using var scope = scopeFactory.CreateAsyncScope();
|
||||
var library = scope.ServiceProvider.GetRequiredService<ILibraryService>();
|
||||
|
||||
var image = await library.FetchImageAsync(imageUrl, cancellationToken);
|
||||
|
||||
if (image.Problem is { } problem)
|
||||
{
|
||||
_problems.OnNext(problem);
|
||||
}
|
||||
|
||||
return image.Path;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_slots.Release();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// A missing cover costs a thumbnail. Nothing above this is waiting for an answer,
|
||||
// so there is nobody to report it to but the log.
|
||||
logger.LogDebug(ex, "Could not fetch the picture at {Url}", imageUrl);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_problems.Dispose();
|
||||
_slots.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user