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; } }