using AvParser.Core.Collecting;
using AvParser.Core.DependencyInjection;
using AvParser.Core.Proxies;
using AvParser.Core.Settings;
using AvParser.Infrastructure.Collecting;
using AvParser.Infrastructure.Media;
using AvParser.Infrastructure.Proxies;
using AvParser.Infrastructure.Settings;
using AvParser.Infrastructure.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace AvParser.Infrastructure.DependencyInjection;
/// Composition root for the infrastructure layer.
public static class InfrastructureServiceCollectionExtensions
{
/// Registers filesystem paths and the persisted settings service.
/// The collection to add to.
///
/// Explicit paths, or to use the current user's application-data folder.
/// Tests pass a temp directory here.
///
public static IServiceCollection AddAvParserInfrastructure(this IServiceCollection services, AppPaths? paths = null)
{
ArgumentNullException.ThrowIfNull(services);
var resolved = paths ?? new AppPaths();
resolved.EnsureCreated();
services.AddSingleton(resolved);
// Constructed explicitly rather than by type: the optional ISequencer parameter would
// otherwise make the container's constructor choice depend on registration order.
services.AddSingleton(sp => new JsonSettingsService(
sp.GetRequiredService(),
sp.GetRequiredService>()
));
services.AddAvParserProxies();
services.AddAvParserCollecting();
return services;
}
/// Registers the media store, the download pipeline and the network sources.
///
/// Everything here is a singleton because everything here owns something shared: a database
/// connection pool, a per-host throttle whose whole purpose is being common to all workers, and
/// a blob directory that must have exactly one owner deciding what is complete.
///
public static IServiceCollection AddAvParserCollecting(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.AddSingleton();
services.AddSingleton();
// Explicit factories: both take an optional parameter, and letting the container pick a
// constructor by registration order is how that goes wrong quietly.
services.AddSingleton(sp => new ShowcaseLinker(
sp.GetRequiredService(),
sp.GetRequiredService(),
sp.GetRequiredService>()
));
services.AddSingleton();
services.AddSingleton(sp => sp.GetRequiredService());
// Two concurrent requests per origin, a quarter of a second apart. These become settings in
// their own right; until then the defaults are the polite ones.
services.AddSingleton(sp => new HostThrottle(
maxConcurrentPerHost: 2,
minimumInterval: TimeSpan.FromMilliseconds(250),
sp.GetRequiredService>()
));
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
// Resolves sources from both assemblies: the container gathers every IMediaSource
// registration regardless of which project declared it.
services.AddSingleton(sp => new MediaSourceCatalog(
sp.GetServices(),
CoreServiceCollectionExtensions.DefaultMediaSourceId
));
return services;
}
/// Registers the proxy sources, the probe, the pool and the proxied client factory.
///
/// The feed source gets a pooled because it always talks to the same
/// CDN host. The probe deliberately does not: its handler carries the proxy, so it has to
/// build one per check.
///
public static IServiceCollection AddAvParserProxies(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services
.AddHttpClient(client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.UserAgent.ParseAdd("AvParser/0.1");
})
.ConfigurePrimaryHttpMessageHandler(() =>
new SocketsHttpHandler { AutomaticDecompression = System.Net.DecompressionMethods.All }
);
services.AddSingleton();
services.AddSingleton(sp => sp.GetRequiredService());
// Registration order here is the order the pool merges sources; the custom list comes
// last so a user-entered address wins over a feed entry for the same host and port.
services.AddSingleton(sp => sp.GetRequiredService());
services.AddSingleton(sp => sp.GetRequiredService());
services.AddSingleton();
services.AddSingleton(sp => new ProxyPool(
sp.GetServices(),
sp.GetRequiredService(),
sp.GetRequiredService().Current.ToProxyOptions()
));
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
return services;
}
}