Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<!-- SQLitePCLRaw.lib.e_sqlite3 2.1.11 has CVE-2025-6965 with no patched version -->
<!-- SQLitePCLRaw.lib_e_sqlite3 2.1.11 has CVE-2025-6965 with no patched version -->
<NoWarn>$(NoWarn);NU1903</NoWarn>
</PropertyGroup>
</Project>
19 changes: 19 additions & 0 deletions src/ArmMedia.Core/DiagnosticNameAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace ArmMedia.Core;

/// <summary>
/// Marks a class with a diagnostic name that dotnet-monitor can use to filter
/// log output by category. The <see cref="Name"/> must match the logger category
/// passed to <c>ILoggerFactory.CreateLogger</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class DiagnosticNameAttribute : Attribute
{
/// <summary>The diagnostic name / logger category.</summary>
public string Name { get; }

/// <summary>Creates the attribute with the given diagnostic name.</summary>
public DiagnosticNameAttribute(string name)
{
Name = name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,25 @@ namespace ArmMedia.Core.Orchestration;
/// Runs the registered provider pipeline in configured order, merges results by
/// confidence, then applies multi-part and extras post-processing.
/// </summary>
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed class EpisodeIdentificationOrchestrator : IEpisodeIdentificationOrchestrator
{
private const string DiagnosticCategory = "EpisodeIdentificationOrchestrator";
private readonly IReadOnlyList<IEpisodeIdentificationProvider> _providers;
private readonly EpisodeIdentificationOptions _options;
private readonly ILogger<EpisodeIdentificationOrchestrator> _logger;
private readonly ILogger _logger;

/// <summary>Initialises the orchestrator with a DI-injected provider list.</summary>
public EpisodeIdentificationOrchestrator(
IEnumerable<IEpisodeIdentificationProvider> providers,
IOptions<EpisodeIdentificationOptions> options,
ILogger<EpisodeIdentificationOrchestrator> logger)
ILoggerFactory loggerFactory)
{
_providers = providers
.OrderBy(p => options.Value.ProviderOrder.IndexOf(p.ProviderName) is int i && i >= 0 ? i : int.MaxValue)
.ToList();
_options = options.Value;
_logger = logger;
_logger = loggerFactory.CreateLogger(DiagnosticCategory);

_logger.LogInformation("Provider execution order: {Order}",
string.Join(" → ", _providers.Select(p => p.ProviderName)));
Expand Down
8 changes: 5 additions & 3 deletions src/ArmMedia.DiscDbProvider/DiscDbProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@ namespace ArmMedia.DiscDbProvider;
/// When a matching DiscDb record is found, returns results with
/// <see cref="Confidence.Definitive"/>, causing the orchestrator to short-circuit.
/// </summary>
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed class DiscDbProvider : IEpisodeIdentificationProvider
{
private const string DiagnosticCategory = "DiscDbProvider";
private readonly IDiscDbLookupService _discDbLookup;
private readonly ILogger<DiscDbProvider> _logger;
private readonly ILogger _logger;

/// <summary>Initialises the provider with a DiscDb lookup service.</summary>
public DiscDbProvider(
IDiscDbLookupService discDbLookup,
ILogger<DiscDbProvider> logger)
ILoggerFactory loggerFactory)
{
_discDbLookup = discDbLookup;
_logger = logger;
_logger = loggerFactory.CreateLogger(DiagnosticCategory);
}

/// <inheritdoc/>
Expand Down
8 changes: 5 additions & 3 deletions src/ArmMedia.DvdCompareProvider/DvdCompareProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,22 @@ namespace ArmMedia.DvdCompareProvider;
/// MakeMKV produces generic track filenames but the episode structure and
/// runtimes are well-documented on dvdcompare.net.
/// </summary>
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed partial class DvdCompareProvider : IEpisodeIdentificationProvider
{
private const string DiagnosticCategory = "DvdCompareProvider";
private readonly DvdCompareProviderOptions _options;
private readonly ILogger<DvdCompareProvider> _logger;
private readonly ILogger _logger;
private readonly IHttpClientFactory? _httpClientFactory;

/// <summary>Initialises the provider with options, logger, and optional HTTP client factory.</summary>
public DvdCompareProvider(
IOptions<DvdCompareProviderOptions> options,
ILogger<DvdCompareProvider> logger,
ILoggerFactory loggerFactory,
IHttpClientFactory? httpClientFactory = null)
{
_options = options.Value;
_logger = logger;
_logger = loggerFactory.CreateLogger(DiagnosticCategory);
_httpClientFactory = httpClientFactory;
}

Expand Down
8 changes: 5 additions & 3 deletions src/ArmMedia.FileBotProvider/FileBotCliService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ namespace ArmMedia.FileBotProvider;
/// Uses <c>filebot -rename --action test</c> in dry-run mode to get episode
/// mappings without modifying files on disk.
/// </summary>
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed partial class FileBotCliService
{
private const string DiagnosticCategory = "FileBotCliService";
private readonly FileBotCliRunner? _runner;
private readonly FileBotProviderOptions _options;
private readonly ILogger<FileBotCliService> _logger;
private readonly ILogger _logger;

/// <summary>
/// Regex that matches a single filebot dry-run line:
Expand All @@ -30,11 +32,11 @@ public sealed partial class FileBotCliService
/// <summary>Initialises the service with an optional CLI runner, options, and logger.</summary>
public FileBotCliService(
FileBotCliRunner? runner,
ILogger<FileBotCliService> logger,
ILoggerFactory loggerFactory,
IOptions<FileBotProviderOptions> options)
{
_runner = runner;
_logger = logger;
_logger = loggerFactory.CreateLogger(DiagnosticCategory);
_options = options.Value;
}

Expand Down
8 changes: 5 additions & 3 deletions src/ArmMedia.FileBotProvider/FileBotProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ namespace ArmMedia.FileBotProvider;
/// If the sidecar file is absent, this provider returns an empty result set
/// and logs a debug message — it never throws.
/// </summary>
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed class FileBotProvider : IEpisodeIdentificationProvider
{
private const string DiagnosticCategory = "FileBotProvider";
/// <summary>Constant provider name used by both the sidecar and CLI identification paths.</summary>
public const string ProviderNameConst = "FileBot";

private readonly FileBotProviderOptions _options;
private readonly ILogger<FileBotProvider> _logger;
private readonly ILogger _logger;
private readonly FileBotCliService? _cliService;

private static readonly JsonSerializerOptions _jsonOptions = new()
Expand All @@ -30,11 +32,11 @@ public sealed class FileBotProvider : IEpisodeIdentificationProvider
/// <summary>Initialises the provider with options, a logger, and an optional CLI service for runtime identification.</summary>
public FileBotProvider(
IOptions<FileBotProviderOptions> options,
ILogger<FileBotProvider> logger,
ILoggerFactory loggerFactory,
FileBotCliService? cliService = null)
{
_options = options.Value;
_logger = logger;
_logger = loggerFactory.CreateLogger(DiagnosticCategory);
_cliService = cliService;
}

Expand Down
8 changes: 5 additions & 3 deletions src/ArmMedia.Linting/DefaultLintingEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ namespace ArmMedia.Linting;
/// Default implementation of <see cref="ILintingEngine"/> that evaluates all
/// registered <see cref="ILintRule"/> implementations against the episode map.
/// </summary>
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed class DefaultLintingEngine : ILintingEngine
{
private const string DiagnosticCategory = "DefaultLintingEngine";
private readonly IReadOnlyList<ILintRule> _rules;
private readonly ILogger<DefaultLintingEngine> _logger;
private readonly ILogger _logger;

/// <summary>Initialises the engine with DI-injected rules.</summary>
public DefaultLintingEngine(
IEnumerable<ILintRule> rules,
ILogger<DefaultLintingEngine> logger)
ILoggerFactory loggerFactory)
{
_rules = rules.ToList();
_logger = logger;
_logger = loggerFactory.CreateLogger(DiagnosticCategory);
}

/// <inheritdoc/>
Expand Down
8 changes: 5 additions & 3 deletions src/ArmMedia.OmdbProvider/OmdbProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@ namespace ArmMedia.OmdbProvider;
/// Uses the already-configured OMDB API key from host settings.
/// Returns results with <see cref="Confidence.High"/>.
/// </summary>
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed class OmdbProvider : IEpisodeIdentificationProvider
{
private const string DiagnosticCategory = "OmdbProvider";
private readonly IOmdbApiKeySource _apiKeySource;
private readonly ILogger<OmdbProvider> _logger;
private readonly ILogger _logger;
private readonly IHttpClientFactory? _httpClientFactory;

/// <summary>Initialises the provider with an API key source and logger.</summary>
public OmdbProvider(
IOmdbApiKeySource apiKeySource,
ILogger<OmdbProvider> logger,
ILoggerFactory loggerFactory,
IHttpClientFactory? httpClientFactory = null)
{
_apiKeySource = apiKeySource;
_logger = logger;
_logger = loggerFactory.CreateLogger(DiagnosticCategory);
_httpClientFactory = httpClientFactory;
}

Expand Down
8 changes: 5 additions & 3 deletions src/ArmMedia.TmdbProvider/TmdbProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,24 @@ namespace ArmMedia.TmdbProvider;
/// Returns results with <see cref="Confidence.High"/> when a matching series
/// is found and episodes can be assigned.
/// </summary>
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed class TmdbProvider : IEpisodeIdentificationProvider
{
private const string DiagnosticCategory = "TmdbProvider";
private readonly ITmdbApiKeySource _apiKeySource;
private readonly ILogger<TmdbProvider> _logger;
private readonly ILogger _logger;
private readonly IHttpClientFactory? _httpClientFactory;

private const string BaseUrl = "https://api.themoviedb.org/3";

/// <summary>Initialises the provider with an API key source, logger, and optional HTTP client factory.</summary>
public TmdbProvider(
ITmdbApiKeySource apiKeySource,
ILogger<TmdbProvider> logger,
ILoggerFactory loggerFactory,
IHttpClientFactory? httpClientFactory = null)
{
_apiKeySource = apiKeySource;
_logger = logger;
_logger = loggerFactory.CreateLogger(DiagnosticCategory);
_httpClientFactory = httpClientFactory;
}

Expand Down
8 changes: 5 additions & 3 deletions src/ArmMedia.TvdbProvider/TvdbProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ namespace ArmMedia.TvdbProvider;
/// ordering, which matches the physical track layout on optical discs.
/// Returns results with <see cref="Confidence.High"/>.
/// </summary>
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed class TvdbProvider : IEpisodeIdentificationProvider
{
private const string DiagnosticCategory = "TvdbProvider";
private readonly ITvdbApiKeySource _apiKeySource;
private readonly TvdbProviderOptions _options;
private readonly ILogger<TvdbProvider> _logger;
private readonly ILogger _logger;
private readonly IHttpClientFactory? _httpClientFactory;

// Token is cached in-memory with a simple expiry (TVDB tokens last ~24h).
Expand All @@ -31,12 +33,12 @@ public sealed class TvdbProvider : IEpisodeIdentificationProvider
public TvdbProvider(
ITvdbApiKeySource apiKeySource,
IOptions<TvdbProviderOptions> options,
ILogger<TvdbProvider> logger,
ILoggerFactory loggerFactory,
IHttpClientFactory? httpClientFactory = null)
{
_apiKeySource = apiKeySource;
_options = options.Value;
_logger = logger;
_logger = loggerFactory.CreateLogger(DiagnosticCategory);
_httpClientFactory = httpClientFactory;
}

Expand Down
6 changes: 3 additions & 3 deletions src/ArmRipper.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@

using (var initScope = host.Services.CreateScope())
{
var initLogger = initScope.ServiceProvider.GetRequiredService<ILogger<Program>>();
var initLogger = initScope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("ArmRipper.Cli.Program");
var db = initScope.ServiceProvider.GetRequiredService<ArmDbContext>();

var yamlPath = "/etc/arm/config/arm.yaml";
Expand Down Expand Up @@ -187,7 +187,7 @@
return 1;
}

var logger = host.Services.GetRequiredService<ILogger<Program>>();
var logger = host.Services.GetRequiredService<ILoggerFactory>().CreateLogger("ArmRipper.Cli.Program");
logger.LogInformation("ARM .NET Ripper starting for device {Device}", deviceArg);

using var scope = host.Services.CreateScope();
Expand All @@ -201,7 +201,7 @@

static async Task<int> RunReidentifyJobAsync(IServiceProvider services, int jobId, bool save)
{
var logger = services.GetRequiredService<ILogger<Program>>();
var logger = services.GetRequiredService<ILoggerFactory>().CreateLogger("ArmRipper.Cli.Program");
var db = services.GetRequiredService<ArmDbContext>();
var orchestrator = services.GetRequiredService<IEpisodeIdentificationOrchestrator>();
var settings = services.GetRequiredService<IOptions<ArmSettings>>().Value;
Expand Down
4 changes: 3 additions & 1 deletion src/ArmRipper.Core/Infrastructure/BackgroundRipService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@

namespace ArmRipper.Core.Infrastructure;

[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed class BackgroundRipService(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory, IOptions<ArmSettings> settings)
: IBackgroundRipService
{
private readonly ILogger logger = loggerFactory.CreateLogger("BackgroundRipService");
private const string DiagnosticCategory = "BackgroundRipService";
private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory);
private readonly IOptions<ArmSettings> _settings = settings;
private readonly ConcurrentDictionary<string, CancellationTokenSource> _activeRips = new();
private readonly ConcurrentDictionary<string, DateTime> _ejectCooldowns = new();
Expand Down
4 changes: 3 additions & 1 deletion src/ArmRipper.Core/Infrastructure/CliProcessRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace ArmRipper.Core.Infrastructure;

[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public class CliProcessRunner(ILoggerFactory loggerFactory) : ICliProcessRunner
{
private readonly ILogger logger = loggerFactory.CreateLogger(nameof(CliProcessRunner));
private const string DiagnosticCategory = "CliProcessRunner";
private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory);
public async Task<CliResult> RunAsync(
string fileName,
string arguments,
Expand Down
6 changes: 4 additions & 2 deletions src/ArmRipper.Core/Infrastructure/DiscPollingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace ArmRipper.Core.Infrastructure;
/// When a disc is inserted on a drive in "autodetect" mode, it automatically kicks off
/// a rip via <see cref="IBackgroundRipService"/>.
/// </summary>
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed class DiscPollingService(
IServiceScopeFactory scopeFactory,
IOptions<ArmSettings> settings,
Expand All @@ -28,7 +29,8 @@ public sealed class DiscPollingService(
IBackgroundRipService backgroundRipService)
: BackgroundService, IDiscPollingNotifier
{
private readonly ILogger _logger = loggerFactory.CreateLogger("DiscPollingService");
private const string DiagnosticCategory = "DiscPollingService";
private readonly ILogger _logger = loggerFactory.CreateLogger(DiagnosticCategory);
private readonly IOptions<ArmSettings> _settings = settings;

/// <summary>Seconds to wait after a uevent before reading sysfs.</summary>
Expand Down Expand Up @@ -198,7 +200,7 @@ private async Task<bool> CheckDiscPollingEnabledAsync(CancellationToken ct)
[SupportedOSPlatform("linux")]
private Task<UeventMonitor?> TryStartUeventMonitorAsync(CancellationToken ct)
{
var logger = loggerFactory.CreateLogger<UeventMonitor>();
var logger = loggerFactory.CreateLogger(UeventMonitor.DiagnosticCategory);
var monitor = new UeventMonitor(logger);

if (!monitor.TryStart())
Expand Down
4 changes: 4 additions & 0 deletions src/ArmRipper.Core/Infrastructure/UeventMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ internal sealed record UeventMessage
/// returns <c>false</c>.
/// </summary>
[SupportedOSPlatform("linux")]
[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
internal sealed class UeventMonitor : IDisposable
{
/// <summary>DiagnosticSource category name for dotnet-monitor filtering.</summary>
internal const string DiagnosticCategory = "UeventMonitor";

// ── Linux constants ──────────────────────────────────────
private const int AF_NETLINK = 16;
private const int SOCK_RAW = 3;
Expand Down
4 changes: 3 additions & 1 deletion src/ArmRipper.Core/Metadata/OmdbService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace ArmRipper.Core.Metadata;

[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed class OmdbService(ILoggerFactory loggerFactory, HttpClient httpClient)
{
private readonly ILogger logger = loggerFactory.CreateLogger("OmdbService");
private const string DiagnosticCategory = "OmdbService";
private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory);
public async Task<OmdbSearchResult?> SearchAsync(string apiKey, string title, string? year = null, string? plot = "short", CancellationToken ct = default)
{
var url = $"https://www.omdbapi.com/?s={Uri.EscapeDataString(title)}&plot={plot}&r=json&apikey={apiKey}";
Expand Down
4 changes: 3 additions & 1 deletion src/ArmRipper.Core/Metadata/TmdbService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace ArmRipper.Core.Metadata;

[ArmMedia.Core.DiagnosticName(DiagnosticCategory)]
public sealed partial class TmdbService(ILoggerFactory loggerFactory, HttpClient httpClient)
{
private readonly ILogger logger = loggerFactory.CreateLogger("TmdbService");
private const string DiagnosticCategory = "TmdbService";
private readonly ILogger logger = loggerFactory.CreateLogger(DiagnosticCategory);
private const string PosterBase = "https://image.tmdb.org/t/p/original";

public async Task<TmdbProcessedResult?> SearchMovieAsync(string apiKey, string query, string? year = null, CancellationToken ct = default)
Expand Down
Loading