diff --git a/code/Common/Configuration/Arr/ArrConfig.cs b/code/Common/Configuration/Arr/ArrConfig.cs
index 7aed605c..56626109 100644
--- a/code/Common/Configuration/Arr/ArrConfig.cs
+++ b/code/Common/Configuration/Arr/ArrConfig.cs
@@ -1,9 +1,14 @@
-using Microsoft.Extensions.Configuration;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
namespace Common.Configuration.Arr;
public abstract class ArrConfig : IConfig
{
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public Guid Id { get; init; } = Guid.NewGuid();
+
public bool Enabled { get; init; }
public short FailedImportMaxStrikes { get; init; } = -1;
diff --git a/code/Common/Configuration/Arr/ArrInstance.cs b/code/Common/Configuration/Arr/ArrInstance.cs
index f2c5136c..f0fc236c 100644
--- a/code/Common/Configuration/Arr/ArrInstance.cs
+++ b/code/Common/Configuration/Arr/ArrInstance.cs
@@ -1,10 +1,13 @@
-using Common.Attributes;
-using Common.Configuration;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Common.Attributes;
namespace Common.Configuration.Arr;
public sealed class ArrInstance
{
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; } = Guid.NewGuid();
public required string Name { get; set; }
diff --git a/code/Common/Configuration/DownloadCleaner/CleanCategory.cs b/code/Common/Configuration/DownloadCleaner/CleanCategory.cs
index df9f6cf9..f7354aac 100644
--- a/code/Common/Configuration/DownloadCleaner/CleanCategory.cs
+++ b/code/Common/Configuration/DownloadCleaner/CleanCategory.cs
@@ -1,9 +1,15 @@
-using Common.Exceptions;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using ValidationException = Common.Exceptions.ValidationException;
namespace Common.Configuration.DownloadCleaner;
public sealed record CleanCategory : IConfig
{
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public Guid Id { get; init; } = Guid.NewGuid();
+
public required string Name { get; init; }
///
diff --git a/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs b/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs
index f77b14b8..9641edc2 100644
--- a/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs
+++ b/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs
@@ -4,6 +4,8 @@ namespace Common.Configuration.DownloadCleaner;
public sealed record DownloadCleanerConfig : IJobConfig
{
+ public Guid Id { get; init; } = Guid.NewGuid();
+
public bool Enabled { get; init; }
public string CronExpression { get; init; } = "0 0 * * * ?";
@@ -11,7 +13,7 @@ public sealed record DownloadCleanerConfig : IJobConfig
///
/// Indicates whether to use the CronExpression directly or convert from a user-friendly schedule
///
- public bool UseAdvancedScheduling { get; init; } = false;
+ public bool UseAdvancedScheduling { get; init; }
public List Categories { get; init; } = [];
diff --git a/code/Common/Configuration/DownloadClient/ClientConfig.cs b/code/Common/Configuration/DownloadClient.cs
similarity index 78%
rename from code/Common/Configuration/DownloadClient/ClientConfig.cs
rename to code/Common/Configuration/DownloadClient.cs
index c2db665d..565863e6 100644
--- a/code/Common/Configuration/DownloadClient/ClientConfig.cs
+++ b/code/Common/Configuration/DownloadClient.cs
@@ -1,29 +1,31 @@
+using System.ComponentModel.DataAnnotations.Schema;
using Common.Attributes;
using Common.Enums;
using Common.Exceptions;
using Newtonsoft.Json;
-namespace Common.Configuration.DownloadClient;
+namespace Common.Configuration;
///
/// Configuration for a specific download client
///
-public sealed record ClientConfig
+public sealed record DownloadClient
{
- ///
- /// Whether this client is enabled
- ///
- public bool Enabled { get; init; } = true;
-
///
/// Unique identifier for this client
///
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; init; } = Guid.NewGuid();
+ ///
+ /// Whether this client is enabled
+ ///
+ public bool Enabled { get; init; } = false;
+
///
/// Friendly name for this client
///
- public string Name { get; init; } = string.Empty;
+ public required string Name { get; init; }
///
/// Type of download client
@@ -39,19 +41,19 @@ public sealed record ClientConfig
/// Username for authentication
///
[SensitiveData]
- public string Username { get; init; } = string.Empty;
+ public string? Username { get; init; }
///
/// Password for authentication
///
[SensitiveData]
- public string Password { get; init; } = string.Empty;
+ public string? Password { get; init; }
///
/// The base URL path component, used by clients like Transmission and Deluge
///
[JsonProperty("url_base")]
- public string UrlBase { get; init; } = string.Empty;
+ public string? UrlBase { get; init; }
///
/// The computed full URL for the client
@@ -63,11 +65,6 @@ public sealed record ClientConfig
///
public void Validate()
{
- if (Id == Guid.Empty)
- {
- throw new ValidationException("Client ID cannot be empty");
- }
-
if (string.IsNullOrWhiteSpace(Name))
{
throw new ValidationException($"Client name cannot be empty for client ID: {Id}");
diff --git a/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs b/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs
deleted file mode 100644
index cdcc898e..00000000
--- a/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using Common.Exceptions;
-
-namespace Common.Configuration.DownloadClient;
-
-public sealed record DownloadClientConfig : IConfig
-{
- ///
- /// Collection of download clients configured for the application
- ///
- public List Clients { get; init; } = new();
-
- ///
- /// Gets a client configuration by id
- ///
- /// The client id
- /// The client configuration or null if not found
- public ClientConfig? GetClientConfig(Guid id)
- {
- return Clients.FirstOrDefault(c => c.Id == id);
- }
-
- ///
- /// Gets all enabled clients
- ///
- /// Collection of enabled client configurations
- public IEnumerable GetEnabledClients()
- {
- return Clients.Where(c => c.Enabled);
- }
-
- ///
- /// Validates the configuration to ensure it meets requirements
- ///
- public void Validate()
- {
- // Validate clients have unique IDs
- var duplicateNames = Clients
- .GroupBy(c => c.Name)
- .Where(g => g.Count() > 1)
- .Select(g => g.Key)
- .ToList();
-
- if (duplicateNames.Any())
- {
- throw new ValidationException($"Duplicate client names found: {string.Join(", ", duplicateNames)}");
- }
-
- // Validate each client configuration
- foreach (var client in Clients)
- {
- client.Validate();
- }
- }
-}
\ No newline at end of file
diff --git a/code/Common/Configuration/General/DryRunConfig.cs b/code/Common/Configuration/General/DryRunConfig.cs
deleted file mode 100644
index 22b9c419..00000000
--- a/code/Common/Configuration/General/DryRunConfig.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Microsoft.Extensions.Configuration;
-
-namespace Common.Configuration.General;
-
-public sealed record DryRunConfig
-{
- [ConfigurationKeyName("DRY_RUN")]
- public bool IsDryRun { get; init; }
-}
\ No newline at end of file
diff --git a/code/Common/Configuration/General/GeneralConfig.cs b/code/Common/Configuration/General/GeneralConfig.cs
index 8c6964eb..f2f14181 100644
--- a/code/Common/Configuration/General/GeneralConfig.cs
+++ b/code/Common/Configuration/General/GeneralConfig.cs
@@ -1,26 +1,32 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
using Common.Enums;
-using Common.Exceptions;
using Serilog.Events;
+using ValidationException = Common.Exceptions.ValidationException;
namespace Common.Configuration.General;
public sealed record GeneralConfig : IConfig
{
- public bool DryRun { get; init; }
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public Guid Id { get; set; } = Guid.NewGuid();
- public ushort HttpMaxRetries { get; init; }
+ public bool DryRun { get; set; }
- public ushort HttpTimeout { get; init; } = 100;
+ public ushort HttpMaxRetries { get; set; }
- public CertificateValidationType HttpCertificateValidation { get; init; } = CertificateValidationType.Enabled;
+ public ushort HttpTimeout { get; set; } = 100;
+
+ public CertificateValidationType HttpCertificateValidation { get; set; } = CertificateValidationType.Enabled;
- public bool SearchEnabled { get; init; } = true;
+ public bool SearchEnabled { get; set; } = true;
- public ushort SearchDelay { get; init; } = 30;
+ public ushort SearchDelay { get; set; } = 30;
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
- public string EncryptionKey { get; init; } = Guid.NewGuid().ToString();
+ public string EncryptionKey { get; set; } = Guid.NewGuid().ToString();
public List IgnoredDownloads { get; set; } = [];
diff --git a/code/Common/Configuration/Notification/AppriseConfig.cs b/code/Common/Configuration/Notification/AppriseConfig.cs
index 6b26eff0..bdbfeaea 100644
--- a/code/Common/Configuration/Notification/AppriseConfig.cs
+++ b/code/Common/Configuration/Notification/AppriseConfig.cs
@@ -1,6 +1,6 @@
namespace Common.Configuration.Notification;
-public sealed record AppriseConfig : BaseNotificationConfig
+public sealed record AppriseConfig : NotificationConfig
{
public Uri? Url { get; init; }
diff --git a/code/Common/Configuration/Notification/NotifiarrConfig.cs b/code/Common/Configuration/Notification/NotifiarrConfig.cs
index 72b6633f..b52b6cbd 100644
--- a/code/Common/Configuration/Notification/NotifiarrConfig.cs
+++ b/code/Common/Configuration/Notification/NotifiarrConfig.cs
@@ -1,15 +1,9 @@
-using Microsoft.Extensions.Configuration;
-
namespace Common.Configuration.Notification;
-public sealed record NotifiarrConfig : BaseNotificationConfig
+public sealed record NotifiarrConfig : NotificationConfig
{
- public const string SectionName = "Notifiarr";
-
- [ConfigurationKeyName("API_KEY")]
public string? ApiKey { get; init; }
- [ConfigurationKeyName("CHANNEL_ID")]
public string? ChannelId { get; init; }
public override bool IsValid()
diff --git a/code/Common/Configuration/Notification/BaseNotificationConfig.cs b/code/Common/Configuration/Notification/NotificationConfig.cs
similarity index 92%
rename from code/Common/Configuration/Notification/BaseNotificationConfig.cs
rename to code/Common/Configuration/Notification/NotificationConfig.cs
index 401386ca..781dda0c 100644
--- a/code/Common/Configuration/Notification/BaseNotificationConfig.cs
+++ b/code/Common/Configuration/Notification/NotificationConfig.cs
@@ -1,6 +1,6 @@
namespace Common.Configuration.Notification;
-public abstract record BaseNotificationConfig
+public abstract record NotificationConfig
{
public bool OnFailedImportStrike { get; init; }
diff --git a/code/Common/Configuration/Notification/NotificationsConfig.cs b/code/Common/Configuration/Notification/NotificationsConfig.cs
deleted file mode 100644
index 98556730..00000000
--- a/code/Common/Configuration/Notification/NotificationsConfig.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Common.Configuration.Notification;
-
-public sealed record NotificationsConfig
-{
- public NotifiarrConfig Notifiarr { get; init; } = new();
-
- public AppriseConfig Apprise { get; init; } = new();
-}
\ No newline at end of file
diff --git a/code/Common/Configuration/QueueCleaner/BlocklistSettings.cs b/code/Common/Configuration/QueueCleaner/BlocklistSettings.cs
index 536da6b9..db1e313a 100644
--- a/code/Common/Configuration/QueueCleaner/BlocklistSettings.cs
+++ b/code/Common/Configuration/QueueCleaner/BlocklistSettings.cs
@@ -1,8 +1,11 @@
+using System.ComponentModel.DataAnnotations.Schema;
+
namespace Common.Configuration.QueueCleaner;
///
/// Settings for a blocklist
///
+[ComplexType]
public sealed record BlocklistSettings
{
public BlocklistType BlocklistType { get; init; }
diff --git a/code/Common/Configuration/QueueCleaner/ContentBlockerConfig.cs b/code/Common/Configuration/QueueCleaner/ContentBlockerConfig.cs
index 6571efb0..6275af59 100644
--- a/code/Common/Configuration/QueueCleaner/ContentBlockerConfig.cs
+++ b/code/Common/Configuration/QueueCleaner/ContentBlockerConfig.cs
@@ -1,5 +1,8 @@
+using System.ComponentModel.DataAnnotations.Schema;
+
namespace Common.Configuration.QueueCleaner;
+[ComplexType]
public sealed record ContentBlockerConfig
{
public bool Enabled { get; init; }
diff --git a/code/Common/Configuration/QueueCleaner/FailedImportConfig.cs b/code/Common/Configuration/QueueCleaner/FailedImportConfig.cs
index e80fa7aa..6945b956 100644
--- a/code/Common/Configuration/QueueCleaner/FailedImportConfig.cs
+++ b/code/Common/Configuration/QueueCleaner/FailedImportConfig.cs
@@ -1,7 +1,9 @@
-using Common.Exceptions;
+using System.ComponentModel.DataAnnotations.Schema;
+using Common.Exceptions;
namespace Common.Configuration.QueueCleaner;
+[ComplexType]
public sealed record FailedImportConfig
{
public ushort MaxStrikes { get; init; }
diff --git a/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs b/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs
index 4245f313..33da03a7 100644
--- a/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs
+++ b/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs
@@ -1,7 +1,11 @@
+using System.ComponentModel.DataAnnotations;
+
namespace Common.Configuration.QueueCleaner;
public sealed record QueueCleanerConfig : IJobConfig
{
+ public Guid Id { get; init; } = Guid.NewGuid();
+
public bool Enabled { get; init; }
public string CronExpression { get; init; } = "0 0/5 * * * ?";
diff --git a/code/Common/Configuration/QueueCleaner/SlowConfig.cs b/code/Common/Configuration/QueueCleaner/SlowConfig.cs
index 5d4f8f2c..2a80b2fa 100644
--- a/code/Common/Configuration/QueueCleaner/SlowConfig.cs
+++ b/code/Common/Configuration/QueueCleaner/SlowConfig.cs
@@ -1,9 +1,11 @@
-using System.Text.Json.Serialization;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
using Common.CustomDataTypes;
using Common.Exceptions;
namespace Common.Configuration.QueueCleaner;
+[ComplexType]
public sealed record SlowConfig
{
public ushort MaxStrikes { get; init; }
diff --git a/code/Common/Configuration/QueueCleaner/StalledConfig.cs b/code/Common/Configuration/QueueCleaner/StalledConfig.cs
index d418a113..ae2ce862 100644
--- a/code/Common/Configuration/QueueCleaner/StalledConfig.cs
+++ b/code/Common/Configuration/QueueCleaner/StalledConfig.cs
@@ -1,7 +1,9 @@
-using Common.Exceptions;
+using System.ComponentModel.DataAnnotations.Schema;
+using Common.Exceptions;
namespace Common.Configuration.QueueCleaner;
+[ComplexType]
public sealed record StalledConfig
{
public ushort MaxStrikes { get; init; }
diff --git a/code/Data/DataContext.cs b/code/Data/DataContext.cs
index b386abb6..02926e95 100644
--- a/code/Data/DataContext.cs
+++ b/code/Data/DataContext.cs
@@ -1,4 +1,9 @@
-using Common.Configuration.QueueCleaner;
+using Common.Configuration;
+using Common.Configuration.Arr;
+using Common.Configuration.DownloadCleaner;
+using Common.Configuration.General;
+using Common.Configuration.Notification;
+using Common.Configuration.QueueCleaner;
using Common.Helpers;
using Microsoft.EntityFrameworkCore;
@@ -9,8 +14,24 @@ namespace Data;
///
public class DataContext : DbContext
{
+ public DbSet GeneralConfigs { get; set; }
+
+ public DbSet DownloadClients { get; set; }
+
public DbSet QueueCleanerConfigs { get; set; }
+ public DbSet DownloadCleanerConfigs { get; set; }
+
+ public DbSet SonarrConfigs { get; set; }
+
+ public DbSet RadarrConfigs { get; set; }
+
+ public DbSet LidarrConfigs { get; set; }
+
+ public DbSet AppriseConfigs { get; set; }
+
+ public DbSet NotifiarrConfigs { get; set; }
+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured)
@@ -27,5 +48,37 @@ public class DataContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
+ modelBuilder.Entity(entity =>
+ {
+ entity.ComplexProperty(e => e.FailedImport);
+ entity.ComplexProperty(e => e.Stalled);
+ entity.ComplexProperty(e => e.Slow);
+ entity.ComplexProperty(e => e.ContentBlocker);
+ });
+
+ foreach (var entityType in modelBuilder.Model.GetEntityTypes())
+ {
+ var enumProperties = entityType.ClrType.GetProperties()
+ .Where(p => p.PropertyType.IsEnum ||
+ (p.PropertyType.IsGenericType &&
+ p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) &&
+ p.PropertyType.GetGenericArguments()[0].IsEnum));
+
+ foreach (var property in enumProperties)
+ {
+ modelBuilder.Entity(entityType.ClrType)
+ .Property(property.Name)
+ .HasConversion();
+ }
+ }
+
+ modelBuilder.Entity().HasData(new QueueCleanerConfig());
+ modelBuilder.Entity().HasData(new DownloadCleanerConfig());
+ modelBuilder.Entity().HasData(new GeneralConfig());
+ modelBuilder.Entity().HasData(new SonarrConfig());
+ modelBuilder.Entity().HasData(new RadarrConfig());
+ modelBuilder.Entity().HasData(new LidarrConfig());
+ modelBuilder.Entity().HasData(new AppriseConfig());
+ modelBuilder.Entity().HasData(new NotifiarrConfig());
}
}
\ No newline at end of file
diff --git a/code/Data/EventsContext.cs b/code/Data/EventsContext.cs
index b3faaad7..2ff18cf6 100644
--- a/code/Data/EventsContext.cs
+++ b/code/Data/EventsContext.cs
@@ -33,6 +33,22 @@ public class EventsContext : DbContext
entity.Property(e => e.Timestamp)
.HasConversion(new UtcDateTimeConverter());
});
+
+ foreach (var entityType in modelBuilder.Model.GetEntityTypes())
+ {
+ var enumProperties = entityType.ClrType.GetProperties()
+ .Where(p => p.PropertyType.IsEnum ||
+ (p.PropertyType.IsGenericType &&
+ p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) &&
+ p.PropertyType.GetGenericArguments()[0].IsEnum));
+
+ foreach (var property in enumProperties)
+ {
+ modelBuilder.Entity(entityType.ClrType)
+ .Property(property.Name)
+ .HasConversion();
+ }
+ }
}
public class UtcDateTimeConverter : ValueConverter
diff --git a/code/Data/Migrations/20250613154308_InitialEvents.Designer.cs b/code/Data/Migrations/20250613154308_InitialEvents.Designer.cs
deleted file mode 100644
index 362f6f3e..00000000
--- a/code/Data/Migrations/20250613154308_InitialEvents.Designer.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-//
-using System;
-using Data;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-
-#nullable disable
-
-namespace Data.Migrations
-{
- [DbContext(typeof(EventsContext))]
- [Migration("20250613154308_InitialEvents")]
- partial class InitialEvents
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder.HasAnnotation("ProductVersion", "9.0.5");
-
- modelBuilder.Entity("Data.Models.Events.AppEvent", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT")
- .HasColumnName("id");
-
- b.Property("Data")
- .HasColumnType("TEXT")
- .HasColumnName("data");
-
- b.Property("EventType")
- .HasColumnType("INTEGER")
- .HasColumnName("event_type");
-
- b.Property("Message")
- .IsRequired()
- .HasMaxLength(1000)
- .HasColumnType("TEXT")
- .HasColumnName("message");
-
- b.Property("Severity")
- .HasColumnType("INTEGER")
- .HasColumnName("severity");
-
- b.Property("Timestamp")
- .HasColumnType("TEXT")
- .HasColumnName("timestamp");
-
- b.Property("TrackingId")
- .HasColumnType("TEXT")
- .HasColumnName("tracking_id");
-
- b.HasKey("Id")
- .HasName("pk_events");
-
- b.HasIndex("EventType")
- .HasDatabaseName("ix_events_event_type");
-
- b.HasIndex("Message")
- .HasDatabaseName("ix_events_message");
-
- b.HasIndex("Severity")
- .HasDatabaseName("ix_events_severity");
-
- b.HasIndex("Timestamp")
- .IsDescending()
- .HasDatabaseName("ix_events_timestamp");
-
- b.ToTable("events", (string)null);
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/code/Data/Migrations/20250613154308_InitialEvents.cs b/code/Data/Migrations/20250613154308_InitialEvents.cs
deleted file mode 100644
index 61454def..00000000
--- a/code/Data/Migrations/20250613154308_InitialEvents.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace Data.Migrations
-{
- ///
- public partial class InitialEvents : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "events",
- columns: table => new
- {
- id = table.Column(type: "TEXT", nullable: false),
- timestamp = table.Column(type: "TEXT", nullable: false),
- event_type = table.Column(type: "INTEGER", nullable: false),
- message = table.Column(type: "TEXT", maxLength: 1000, nullable: false),
- data = table.Column(type: "TEXT", nullable: true),
- severity = table.Column(type: "INTEGER", nullable: false),
- tracking_id = table.Column(type: "TEXT", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("pk_events", x => x.id);
- });
-
- migrationBuilder.CreateIndex(
- name: "ix_events_event_type",
- table: "events",
- column: "event_type");
-
- migrationBuilder.CreateIndex(
- name: "ix_events_message",
- table: "events",
- column: "message");
-
- migrationBuilder.CreateIndex(
- name: "ix_events_severity",
- table: "events",
- column: "severity");
-
- migrationBuilder.CreateIndex(
- name: "ix_events_timestamp",
- table: "events",
- column: "timestamp",
- descending: new bool[0]);
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "events");
- }
- }
-}
diff --git a/code/Data/Migrations/EventsContextModelSnapshot.cs b/code/Data/Migrations/EventsContextModelSnapshot.cs
deleted file mode 100644
index ca7f1ad0..00000000
--- a/code/Data/Migrations/EventsContextModelSnapshot.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-//
-using System;
-using Data;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-
-#nullable disable
-
-namespace Data.Migrations
-{
- [DbContext(typeof(EventsContext))]
- partial class EventsContextModelSnapshot : ModelSnapshot
- {
- protected override void BuildModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder.HasAnnotation("ProductVersion", "9.0.5");
-
- modelBuilder.Entity("Data.Models.Events.AppEvent", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT")
- .HasColumnName("id");
-
- b.Property("Data")
- .HasColumnType("TEXT")
- .HasColumnName("data");
-
- b.Property("EventType")
- .HasColumnType("INTEGER")
- .HasColumnName("event_type");
-
- b.Property("Message")
- .IsRequired()
- .HasMaxLength(1000)
- .HasColumnType("TEXT")
- .HasColumnName("message");
-
- b.Property("Severity")
- .HasColumnType("INTEGER")
- .HasColumnName("severity");
-
- b.Property("Timestamp")
- .HasColumnType("TEXT")
- .HasColumnName("timestamp");
-
- b.Property("TrackingId")
- .HasColumnType("TEXT")
- .HasColumnName("tracking_id");
-
- b.HasKey("Id")
- .HasName("pk_events");
-
- b.HasIndex("EventType")
- .HasDatabaseName("ix_events_event_type");
-
- b.HasIndex("Message")
- .HasDatabaseName("ix_events_message");
-
- b.HasIndex("Severity")
- .HasDatabaseName("ix_events_severity");
-
- b.HasIndex("Timestamp")
- .IsDescending()
- .HasDatabaseName("ix_events_timestamp");
-
- b.ToTable("events", (string)null);
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/code/Executable/Controllers/ConfigurationController.cs b/code/Executable/Controllers/ConfigurationController.cs
index 3f92f5cf..bb6f71c5 100644
--- a/code/Executable/Controllers/ConfigurationController.cs
+++ b/code/Executable/Controllers/ConfigurationController.cs
@@ -1,7 +1,6 @@
using Common.Configuration;
using Common.Configuration.Arr;
using Common.Configuration.DownloadCleaner;
-using Common.Configuration.DownloadClient;
using Common.Configuration.General;
using Common.Configuration.Notification;
using Common.Configuration.QueueCleaner;
@@ -53,7 +52,7 @@ public class ConfigurationController : ControllerBase
[HttpGet("download_client")]
public async Task GetDownloadClientConfig()
{
- var config = await _configManager.GetConfigurationAsync();
+ var config = await _configManager.GetConfigurationAsync();
return Ok(config);
}
@@ -195,13 +194,13 @@ public class ConfigurationController : ControllerBase
}
[HttpPut("download_client")]
- public async Task UpdateDownloadClientConfig(DownloadClientConfig newConfig)
+ public async Task UpdateDownloadClientConfig(DownloadClientConfigs newConfigs)
{
// Validate the configuration
- newConfig.Validate();
+ newConfigs.Validate();
// Persist the configuration
- var result = await _configManager.SaveConfigurationAsync(newConfig);
+ var result = await _configManager.SaveConfigurationAsync(newConfigs);
if (!result)
{
return StatusCode(500, "Failed to save DownloadClient configuration");
diff --git a/code/Executable/Controllers/StatusController.cs b/code/Executable/Controllers/StatusController.cs
index c4241fb8..ae4f564a 100644
--- a/code/Executable/Controllers/StatusController.cs
+++ b/code/Executable/Controllers/StatusController.cs
@@ -1,5 +1,4 @@
using Common.Configuration.Arr;
-using Common.Configuration.DownloadClient;
using Infrastructure.Configuration;
using Infrastructure.Verticals.Arr;
using Infrastructure.Verticals.DownloadClient;
@@ -38,7 +37,7 @@ public class StatusController : ControllerBase
var process = Process.GetCurrentProcess();
// Get configuration
- var downloadClientConfig = await _configManager.GetConfigurationAsync();
+ var downloadClientConfig = await _configManager.GetConfigurationAsync();
var sonarrConfig = await _configManager.GetConfigurationAsync();
var radarrConfig = await _configManager.GetConfigurationAsync();
var lidarrConfig = await _configManager.GetConfigurationAsync();
@@ -91,7 +90,7 @@ public class StatusController : ControllerBase
{
try
{
- var downloadClientConfig = await _configManager.GetConfigurationAsync();
+ var downloadClientConfig = await _configManager.GetConfigurationAsync();
var result = new Dictionary();
// Check for configured clients
diff --git a/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs b/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs
index 1c10069f..eab4dbea 100644
--- a/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs
+++ b/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs
@@ -1,4 +1,4 @@
-using Common.Configuration.DownloadClient;
+using Common.Configuration;
using Common.Enums;
using Infrastructure.Configuration;
using Infrastructure.Health;
@@ -16,7 +16,7 @@ public class HealthCheckServiceFixture : IDisposable
public IConfigManager ConfigManager { get; }
public IDownloadClientFactory ClientFactory { get; }
public IDownloadService MockClient { get; }
- public DownloadClientConfig DownloadClientConfig { get; }
+ public DownloadClientConfigs DownloadClientConfigs { get; }
public HealthCheckServiceFixture()
{
@@ -27,9 +27,9 @@ public class HealthCheckServiceFixture : IDisposable
Guid clientId = Guid.NewGuid();
// Set up test download client config
- DownloadClientConfig = new DownloadClientConfig
+ DownloadClientConfigs = new DownloadClientConfigs
{
- Clients = new List
+ Clients = new List
{
new()
{
@@ -64,7 +64,7 @@ public class HealthCheckServiceFixture : IDisposable
MockClient.GetClientId().Returns(clientId);
// Set up mock config manager
- ConfigManager.GetConfiguration().Returns(DownloadClientConfig);
+ ConfigManager.GetConfiguration().Returns(DownloadClientConfigs);
}
public HealthCheckService CreateSut()
diff --git a/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs b/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs
index e79b14c3..827ef227 100644
--- a/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs
+++ b/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs
@@ -1,4 +1,3 @@
-using Common.Configuration.DownloadClient;
using Infrastructure.Health;
using NSubstitute;
using Shouldly;
@@ -59,8 +58,8 @@ public class HealthCheckServiceTests : IClassFixture
var sut = _fixture.CreateSut();
// Configure the ConfigManager to return null for the client config
- _fixture.ConfigManager.GetConfigurationAsync().Returns(
- Task.FromResult(new())
+ _fixture.ConfigManager.GetConfigurationAsync().Returns(
+ Task.FromResult(new())
);
// Act
diff --git a/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs b/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs
index 43ffbc1f..9d21e8e4 100644
--- a/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs
+++ b/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs
@@ -1,4 +1,4 @@
-using Common.Configuration.DownloadClient;
+using Common.Configuration;
using Common.Enums;
using Infrastructure.Configuration;
using Infrastructure.Http;
@@ -30,9 +30,9 @@ public class DynamicHttpClientProviderFixture : IDisposable
certificateValidationService);
}
- public ClientConfig CreateQBitClientConfig()
+ public DownloadClient CreateQBitClientConfig()
{
- return new ClientConfig
+ return new DownloadClient
{
Id = Guid.NewGuid(),
Name = "QBit Test",
@@ -44,9 +44,9 @@ public class DynamicHttpClientProviderFixture : IDisposable
};
}
- public ClientConfig CreateTransmissionClientConfig()
+ public DownloadClient CreateTransmissionClientConfig()
{
- return new ClientConfig
+ return new DownloadClient
{
Id = Guid.NewGuid(),
Name = "Transmission Test",
@@ -59,9 +59,9 @@ public class DynamicHttpClientProviderFixture : IDisposable
};
}
- public ClientConfig CreateDelugeClientConfig()
+ public DownloadClient CreateDelugeClientConfig()
{
- return new ClientConfig
+ return new DownloadClient
{
Id = Guid.NewGuid(),
Name = "Deluge Test",
diff --git a/code/Infrastructure.Tests/Http/DynamicHttpClientProviderTests.cs b/code/Infrastructure.Tests/Http/DynamicHttpClientProviderTests.cs
index 497404cf..be3df3de 100644
--- a/code/Infrastructure.Tests/Http/DynamicHttpClientProviderTests.cs
+++ b/code/Infrastructure.Tests/Http/DynamicHttpClientProviderTests.cs
@@ -1,5 +1,4 @@
using System.Net;
-using Common.Configuration.DownloadClient;
using Common.Enums;
using Infrastructure.Http;
using Shouldly;
diff --git a/code/Infrastructure/Health/HealthCheckService.cs b/code/Infrastructure/Health/HealthCheckService.cs
index 433c7c94..3d073014 100644
--- a/code/Infrastructure/Health/HealthCheckService.cs
+++ b/code/Infrastructure/Health/HealthCheckService.cs
@@ -1,7 +1,6 @@
-using Common.Configuration.DownloadClient;
-using Infrastructure.Configuration;
-using Infrastructure.Verticals.DownloadClient;
+using Data;
using Infrastructure.Verticals.DownloadClient.Factory;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Infrastructure.Health;
@@ -12,7 +11,7 @@ namespace Infrastructure.Health;
public class HealthCheckService : IHealthCheckService
{
private readonly ILogger _logger;
- private readonly IConfigManager _configManager;
+ private readonly DataContext _dataContext;
private readonly IDownloadClientFactory _clientFactory;
private readonly Dictionary _healthStatuses = new();
private readonly object _lockObject = new();
@@ -22,19 +21,13 @@ public class HealthCheckService : IHealthCheckService
///
public event EventHandler? ClientHealthChanged;
- ///
- /// Initializes a new instance of the class
- ///
- /// The logger
- /// The configuration manager
- /// The download client factory
public HealthCheckService(
ILogger logger,
- IConfigManager configManager,
+ DataContext dataContext,
IDownloadClientFactory clientFactory)
{
_logger = logger;
- _configManager = configManager;
+ _dataContext = dataContext;
_clientFactory = clientFactory;
}
@@ -46,8 +39,11 @@ public class HealthCheckService : IHealthCheckService
try
{
// Get the client configuration
- var config = await GetClientConfigAsync(clientId);
- if (config == null)
+ var config = await _dataContext.DownloadClients
+ .Where(x => x.Id == clientId)
+ .FirstOrDefaultAsync();
+
+ if (config is null)
{
_logger.LogWarning("Client {clientId} not found in configuration", clientId);
var notFoundStatus = new HealthStatus
@@ -135,8 +131,9 @@ public class HealthCheckService : IHealthCheckService
try
{
// Get all enabled client configurations
- var config = await _configManager.GetConfigurationAsync();
- var enabledClients = config.GetEnabledClients();
+ var enabledClients = await _dataContext.DownloadClients
+ .Where(x => x.Enabled)
+ .ToListAsync();
var results = new Dictionary();
// Check health of each enabled client
@@ -173,12 +170,6 @@ public class HealthCheckService : IHealthCheckService
}
}
- private async Task GetClientConfigAsync(Guid clientId)
- {
- var config = await _configManager.GetConfigurationAsync();
- return config.GetClientConfig(clientId);
- }
-
private void UpdateHealthStatus(HealthStatus newStatus)
{
HealthStatus? previousStatus = null;
diff --git a/code/Infrastructure/Http/DynamicHttpClientProvider.cs b/code/Infrastructure/Http/DynamicHttpClientProvider.cs
index ea1eef43..e39b5789 100644
--- a/code/Infrastructure/Http/DynamicHttpClientProvider.cs
+++ b/code/Infrastructure/Http/DynamicHttpClientProvider.cs
@@ -1,7 +1,7 @@
using System.Net;
-using Common.Configuration.DownloadClient;
+using Common.Configuration;
using Common.Configuration.General;
-using Infrastructure.Configuration;
+using Data;
using Infrastructure.Services;
using Microsoft.Extensions.Logging;
using Polly;
@@ -15,62 +15,62 @@ namespace Infrastructure.Http;
public class DynamicHttpClientProvider : IDynamicHttpClientProvider
{
private readonly ILogger _logger;
+ private readonly DataContext _dataContext;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IConfigManager _configManager;
private readonly CertificateValidationService _certificateValidationService;
public DynamicHttpClientProvider(
ILogger logger,
+ DataContext dataContext,
IHttpClientFactory httpClientFactory,
- IConfigManager configManager,
CertificateValidationService certificateValidationService)
{
_logger = logger;
+ _dataContext = dataContext;
_httpClientFactory = httpClientFactory;
- _configManager = configManager;
_certificateValidationService = certificateValidationService;
}
///
- public HttpClient CreateClient(ClientConfig clientConfig)
+ public HttpClient CreateClient(DownloadClient downloadClient)
{
- if (clientConfig == null)
+ if (downloadClient == null)
{
- throw new ArgumentNullException(nameof(clientConfig));
+ throw new ArgumentNullException(nameof(downloadClient));
}
// Try to use named client if it exists
try
{
- string clientName = GetClientName(clientConfig);
+ string clientName = GetClientName(downloadClient);
return _httpClientFactory.CreateClient(clientName);
}
catch (InvalidOperationException)
{
- _logger.LogWarning("Named HTTP client for {clientId} not found, creating generic client", clientConfig.Id);
- return CreateGenericClient(clientConfig);
+ _logger.LogWarning("Named HTTP client for {clientId} not found, creating generic client", downloadClient.Id);
+ return CreateGenericClient(downloadClient);
}
}
///
/// Gets the client name for a specific client configuration
///
- /// The client configuration
+ /// The client configuration
/// The client name for use with IHttpClientFactory
- private string GetClientName(ClientConfig clientConfig)
+ private string GetClientName(DownloadClient downloadClient)
{
- return $"DownloadClient_{clientConfig.Id}";
+ return $"DownloadClient_{downloadClient.Id}";
}
///
/// Creates a generic HTTP client with appropriate configuration
///
- /// The client configuration
+ /// The client configuration
/// A configured HttpClient instance
- private HttpClient CreateGenericClient(ClientConfig clientConfig)
+ private HttpClient CreateGenericClient(DownloadClient downloadClient)
{
// TODO
- var httpConfig = _configManager.GetConfiguration();
+ var httpConfig = _dataContext.GeneralConfigs.First();
// Create handler with certificate validation
var handler = new HttpClientHandler
@@ -86,7 +86,7 @@ public class DynamicHttpClientProvider : IDynamicHttpClientProvider
UseDefaultCredentials = false
};
- if (clientConfig.Type == Common.Enums.DownloadClientType.Deluge)
+ if (downloadClient.Type == Common.Enums.DownloadClientType.Deluge)
{
handler.AllowAutoRedirect = true;
handler.UseCookies = true;
@@ -101,13 +101,13 @@ public class DynamicHttpClientProvider : IDynamicHttpClientProvider
};
// Set base address if needed
- if (clientConfig.Url != null)
+ if (downloadClient.Url != null)
{
- client.BaseAddress = clientConfig.Url;
+ client.BaseAddress = downloadClient.Url;
}
_logger.LogDebug("Created generic HTTP client for client {clientId} with base address {baseAddress}",
- clientConfig.Id, client.BaseAddress);
+ downloadClient.Id, client.BaseAddress);
return client;
}
diff --git a/code/Infrastructure/Http/IDynamicHttpClientProvider.cs b/code/Infrastructure/Http/IDynamicHttpClientProvider.cs
index ec410a38..20a6d6f0 100644
--- a/code/Infrastructure/Http/IDynamicHttpClientProvider.cs
+++ b/code/Infrastructure/Http/IDynamicHttpClientProvider.cs
@@ -1,4 +1,4 @@
-using Common.Configuration.DownloadClient;
+using Common.Configuration;
namespace Infrastructure.Http;
@@ -10,7 +10,7 @@ public interface IDynamicHttpClientProvider
///
/// Creates an HTTP client configured for the specified download client
///
- /// The client configuration
+ /// The client configuration
/// A configured HttpClient instance
- HttpClient CreateClient(ClientConfig clientConfig);
+ HttpClient CreateClient(DownloadClient downloadClient);
}
diff --git a/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs b/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs
index 34710f75..0414b7c0 100644
--- a/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs
+++ b/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs
@@ -1,6 +1,4 @@
-using Common.Configuration.DownloadClient;
using Common.Enums;
-using Infrastructure.Configuration;
using Infrastructure.Verticals.DownloadClient.Deluge;
using Infrastructure.Verticals.DownloadClient.QBittorrent;
using Infrastructure.Verticals.DownloadClient.Transmission;
@@ -15,16 +13,13 @@ namespace Infrastructure.Verticals.DownloadClient;
public sealed class DownloadServiceFactory
{
private readonly IServiceProvider _serviceProvider;
- private readonly IConfigManager _configManager;
private readonly ILogger _logger;
public DownloadServiceFactory(
IServiceProvider serviceProvider,
- IConfigManager configManager,
ILogger logger)
{
_serviceProvider = serviceProvider;
- _configManager = configManager;
_logger = logger;
}
@@ -56,21 +51,21 @@ public sealed class DownloadServiceFactory
///
/// Creates a download service using the specified client configuration
///
- /// The client configuration to use
+ /// The client configuration to use
/// An implementation of IDownloadService or null if the client is not available
- public IDownloadService? GetDownloadService(ClientConfig clientConfig)
+ public IDownloadService? GetDownloadService(Common.Configuration.DownloadClient downloadClient)
{
- if (!clientConfig.Enabled)
+ if (!downloadClient.Enabled)
{
- _logger.LogWarning("Download client {clientId} is disabled", clientConfig.Id);
+ _logger.LogWarning("Download client {clientId} is disabled", downloadClient.Id);
return null;
}
- return clientConfig.Type switch
+ return downloadClient.Type switch
{
- DownloadClientType.QBittorrent => CreateClientService(clientConfig),
- DownloadClientType.Deluge => CreateClientService(clientConfig),
- DownloadClientType.Transmission => CreateClientService(clientConfig),
+ DownloadClientType.QBittorrent => CreateClientService(downloadClient),
+ DownloadClientType.Deluge => CreateClientService(downloadClient),
+ DownloadClientType.Transmission => CreateClientService(downloadClient),
_ => null
};
}
@@ -79,12 +74,12 @@ public sealed class DownloadServiceFactory
/// Creates a download client service for a specific client type
///
/// The type of download service to create
- /// The client configuration
+ /// The client configuration
/// An implementation of IDownloadService
- private T CreateClientService(ClientConfig clientConfig) where T : IDownloadService
+ private T CreateClientService(Common.Configuration.DownloadClient downloadClient) where T : IDownloadService
{
var service = _serviceProvider.GetRequiredService();
- service.Initialize(clientConfig);
+ service.Initialize(downloadClient);
return service;
}
}
\ No newline at end of file
diff --git a/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs b/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs
index fa989e97..c90f42f4 100644
--- a/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs
+++ b/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs
@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
-using Common.Configuration.DownloadClient;
using Common.Enums;
using Infrastructure.Configuration;
using Infrastructure.Http;
@@ -51,7 +50,7 @@ public class DownloadClientFactory : IDownloadClientFactory
///
public IEnumerable GetAllEnabledClients()
{
- var downloadClientConfig = _configManager.GetConfiguration();
+ var downloadClientConfig = _configManager.GetConfiguration();
foreach (var client in downloadClientConfig.GetEnabledClients())
{
@@ -62,7 +61,7 @@ public class DownloadClientFactory : IDownloadClientFactory
///
public IEnumerable GetClientsByType(DownloadClientType clientType)
{
- var downloadClientConfig = _configManager.GetConfiguration();
+ var downloadClientConfig = _configManager.GetConfiguration();
foreach (var client in downloadClientConfig.GetEnabledClients().Where(c => c.Type == clientType))
{
@@ -100,7 +99,7 @@ public class DownloadClientFactory : IDownloadClientFactory
private IDownloadService CreateClient(Guid clientId)
{
- var downloadClientConfig = _configManager.GetConfiguration();
+ var downloadClientConfig = _configManager.GetConfiguration();
var clientConfig = downloadClientConfig.GetClientConfig(clientId);
@@ -126,24 +125,24 @@ public class DownloadClientFactory : IDownloadClientFactory
return service;
}
- private QBitService CreateQBitService(ClientConfig clientConfig)
+ private QBitService CreateQBitService(Common.Configuration.DownloadClient downloadClient)
{
var client = _serviceProvider.GetRequiredService();
- client.Initialize(clientConfig);
+ client.Initialize(downloadClient);
return client;
}
- private TransmissionService CreateTransmissionService(ClientConfig clientConfig)
+ private TransmissionService CreateTransmissionService(Common.Configuration.DownloadClient downloadClient)
{
var client = _serviceProvider.GetRequiredService();
- client.Initialize(clientConfig);
+ client.Initialize(downloadClient);
return client;
}
- private DelugeService CreateDelugeService(ClientConfig clientConfig)
+ private DelugeService CreateDelugeService(Common.Configuration.DownloadClient downloadClient)
{
var client = _serviceProvider.GetRequiredService();
- client.Initialize(clientConfig);
+ client.Initialize(downloadClient);
return client;
}
}
diff --git a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs
index 0b00d014..6f5eb20b 100644
--- a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs
+++ b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs
@@ -1,7 +1,6 @@
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Common.Configuration.DownloadCleaner;
-using Common.Configuration.DownloadClient;
using Common.Configuration.QueueCleaner;
using Data.Enums;
using Infrastructure.Interceptors;
@@ -20,8 +19,8 @@ public interface IDownloadService : IDisposable
///
/// Initializes the download service with client-specific configuration
///
- /// The client configuration
- public void Initialize(ClientConfig clientConfig);
+ /// The client configuration
+ public void Initialize(Common.Configuration.DownloadClient downloadClient);
public Task LoginAsync();
diff --git a/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs b/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs
index 772dc563..c83d1ea7 100644
--- a/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs
+++ b/code/Infrastructure/Verticals/DownloadClient/QBittorrent/QBitService.cs
@@ -2,7 +2,6 @@ using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Common.Attributes;
using Common.Configuration.DownloadCleaner;
-using Common.Configuration.DownloadClient;
using Common.Configuration.QueueCleaner;
using Common.CustomDataTypes;
using Common.Helpers;
@@ -48,16 +47,16 @@ public partial class QBitService : DownloadService, IQBitService
}
///
- public override void Initialize(ClientConfig clientConfig)
+ public override void Initialize(Common.Configuration.DownloadClient downloadClient)
{
// Initialize base service first
- base.Initialize(clientConfig);
+ base.Initialize(downloadClient);
// Create QBittorrent client
- _client = new QBittorrentClient(_httpClient, clientConfig.Url);
+ _client = new QBittorrentClient(_httpClient, downloadClient.Url);
_logger.LogInformation("Initialized QBittorrent service for client {clientName} ({clientId})",
- clientConfig.Name, clientConfig.Id);
+ downloadClient.Name, downloadClient.Id);
}
public override async Task LoginAsync()
@@ -67,20 +66,20 @@ public partial class QBitService : DownloadService, IQBitService
throw new InvalidOperationException("QBittorrent client is not initialized");
}
- if (string.IsNullOrEmpty(_clientConfig.Username) && string.IsNullOrEmpty(_clientConfig.Password))
+ if (string.IsNullOrEmpty(_downloadClient.Username) && string.IsNullOrEmpty(_downloadClient.Password))
{
- _logger.LogDebug("No credentials configured for client {clientId}, skipping login", _clientConfig.Id);
+ _logger.LogDebug("No credentials configured for client {clientId}, skipping login", _downloadClient.Id);
return;
}
try
{
- await _client.LoginAsync(_clientConfig.Username, _clientConfig.Password);
- _logger.LogDebug("Successfully logged in to QBittorrent client {clientId}", _clientConfig.Id);
+ await _client.LoginAsync(_downloadClient.Username, _downloadClient.Password);
+ _logger.LogDebug("Successfully logged in to QBittorrent client {clientId}", _downloadClient.Id);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Failed to login to QBittorrent client {clientId}", _clientConfig.Id);
+ _logger.LogError(ex, "Failed to login to QBittorrent client {clientId}", _downloadClient.Id);
throw;
}
}
diff --git a/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs b/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs
index 2474f9f2..a22fd41d 100644
--- a/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs
+++ b/code/Infrastructure/Verticals/DownloadClient/Transmission/TransmissionService.cs
@@ -2,7 +2,6 @@ using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Common.Attributes;
using Common.Configuration.DownloadCleaner;
-using Common.Configuration.DownloadClient;
using Common.Configuration.QueueCleaner;
using Common.CustomDataTypes;
using Common.Helpers;
@@ -69,15 +68,15 @@ public partial class TransmissionService : DownloadService, ITransmissionService
}
///
- public override void Initialize(ClientConfig clientConfig)
+ public override void Initialize(Common.Configuration.DownloadClient downloadClient)
{
// Initialize base service first
- base.Initialize(clientConfig);
+ base.Initialize(downloadClient);
// Ensure client type is correct
- if (clientConfig.Type != Common.Enums.DownloadClientType.Transmission)
+ if (downloadClient.Type != Common.Enums.DownloadClientType.Transmission)
{
- throw new InvalidOperationException($"Cannot initialize TransmissionService with client type {clientConfig.Type}");
+ throw new InvalidOperationException($"Cannot initialize TransmissionService with client type {downloadClient.Type}");
}
if (_httpClient == null)
@@ -86,18 +85,18 @@ public partial class TransmissionService : DownloadService, ITransmissionService
}
// Create the RPC path
- string rpcPath = string.IsNullOrEmpty(clientConfig.UrlBase)
+ string rpcPath = string.IsNullOrEmpty(downloadClient.UrlBase)
? "/rpc"
- : $"/{clientConfig.UrlBase.TrimStart('/').TrimEnd('/')}/rpc";
+ : $"/{downloadClient.UrlBase.TrimStart('/').TrimEnd('/')}/rpc";
// Create full RPC URL
- string rpcUrl = new UriBuilder(clientConfig.Url) { Path = rpcPath }.Uri.ToString();
+ string rpcUrl = new UriBuilder(downloadClient.Url) { Path = rpcPath }.Uri.ToString();
// Create Transmission client
- _client = new Client(_httpClient, rpcUrl, login: clientConfig.Username, password: clientConfig.Password);
+ _client = new Client(_httpClient, rpcUrl, login: downloadClient.Username, password: downloadClient.Password);
_logger.LogInformation("Initialized Transmission service for client {clientName} ({clientId})",
- clientConfig.Name, clientConfig.Id);
+ downloadClient.Name, downloadClient.Id);
}
public override async Task LoginAsync()
@@ -110,11 +109,11 @@ public partial class TransmissionService : DownloadService, ITransmissionService
try
{
await _client.GetSessionInformationAsync();
- _logger.LogDebug("Successfully logged in to Transmission client {clientId}", _clientConfig.Id);
+ _logger.LogDebug("Successfully logged in to Transmission client {clientId}", _downloadClient.Id);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Failed to login to Transmission client {clientId}", _clientConfig.Id);
+ _logger.LogError(ex, "Failed to login to Transmission client {clientId}", _downloadClient.Id);
throw;
}
}
diff --git a/code/Infrastructure/Verticals/DownloadRemover/QueueItemRemover.cs b/code/Infrastructure/Verticals/DownloadRemover/QueueItemRemover.cs
index 4310c7d3..0a93f12b 100644
--- a/code/Infrastructure/Verticals/DownloadRemover/QueueItemRemover.cs
+++ b/code/Infrastructure/Verticals/DownloadRemover/QueueItemRemover.cs
@@ -1,5 +1,5 @@
using Common.Configuration.Arr;
-using Common.Configuration.General;
+using Data;
using Data.Enums;
using Data.Models.Arr;
using Data.Models.Arr.Queue;
@@ -10,25 +10,25 @@ using Infrastructure.Verticals.Context;
using Infrastructure.Verticals.DownloadRemover.Interfaces;
using Infrastructure.Verticals.DownloadRemover.Models;
using Microsoft.Extensions.Caching.Memory;
-using Infrastructure.Configuration;
+using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Verticals.DownloadRemover;
public sealed class QueueItemRemover : IQueueItemRemover
{
- private readonly GeneralConfig _generalConfig;
+ private readonly DataContext _dataContext;
private readonly IMemoryCache _cache;
private readonly ArrClientFactory _arrClientFactory;
private readonly EventPublisher _eventPublisher;
public QueueItemRemover(
- IConfigManager configManager,
+ DataContext dataContext,
IMemoryCache cache,
ArrClientFactory arrClientFactory,
EventPublisher eventPublisher
)
{
- _generalConfig = configManager.GetConfiguration();
+ _dataContext = dataContext;
_cache = cache;
_arrClientFactory = arrClientFactory;
_eventPublisher = eventPublisher;
@@ -39,6 +39,7 @@ public sealed class QueueItemRemover : IQueueItemRemover
{
try
{
+ var generalConfig = await _dataContext.GeneralConfigs.FirstAsync();
var arrClient = _arrClientFactory.GetClient(request.InstanceType);
await arrClient.DeleteQueueItemAsync(request.Instance, request.Record, request.RemoveFromClient, request.DeleteReason);
@@ -52,7 +53,7 @@ public sealed class QueueItemRemover : IQueueItemRemover
// Use the new centralized EventPublisher method
await _eventPublisher.PublishQueueItemDeleted(request.RemoveFromClient, request.DeleteReason);
- if (!_generalConfig.SearchEnabled)
+ if (!generalConfig.SearchEnabled)
{
return;
}
@@ -60,7 +61,7 @@ public sealed class QueueItemRemover : IQueueItemRemover
await arrClient.SearchItemsAsync(request.Instance, [request.SearchItem]);
// prevent tracker spamming
- await Task.Delay(TimeSpan.FromSeconds(_generalConfig.SearchDelay));
+ await Task.Delay(TimeSpan.FromSeconds(generalConfig.SearchDelay));
}
finally
{
diff --git a/code/Infrastructure/Verticals/Jobs/GenericHandler.cs b/code/Infrastructure/Verticals/Jobs/GenericHandler.cs
index f3508203..cc39a8b4 100644
--- a/code/Infrastructure/Verticals/Jobs/GenericHandler.cs
+++ b/code/Infrastructure/Verticals/Jobs/GenericHandler.cs
@@ -1,29 +1,27 @@
using Common.Configuration.Arr;
-using Common.Configuration.DownloadClient;
+using Common.Configuration.DownloadCleaner;
using Common.Configuration.General;
+using Common.Configuration.QueueCleaner;
+using Data;
using Data.Enums;
using Data.Models.Arr;
using Data.Models.Arr.Queue;
-using Infrastructure.Configuration;
using Infrastructure.Events;
using Infrastructure.Verticals.Arr;
+using Infrastructure.Verticals.Context;
using Infrastructure.Verticals.DownloadClient;
using Infrastructure.Verticals.DownloadRemover.Models;
-using Infrastructure.Verticals.Notifications;
using MassTransit;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace Infrastructure.Verticals.Jobs;
-public abstract class GenericHandler : IHandler, IDisposable
+public abstract class GenericHandler : IHandler
{
protected readonly ILogger _logger;
- protected readonly GeneralConfig _generalConfig;
- protected readonly DownloadClientConfig _downloadClientConfig;
- protected readonly SonarrConfig _sonarrConfig;
- protected readonly RadarrConfig _radarrConfig;
- protected readonly LidarrConfig _lidarrConfig;
+ protected readonly DataContext _dataContext;
protected readonly IMemoryCache _cache;
protected readonly IBus _messageBus;
protected readonly ArrClientFactory _arrClientFactory;
@@ -31,9 +29,6 @@ public abstract class GenericHandler : IHandler, IDisposable
protected readonly DownloadServiceFactory _downloadServiceFactory;
private readonly EventPublisher _eventPublisher;
- // Collection of download services for use with multiple clients
- protected readonly List _downloadServices = [];
-
protected GenericHandler(
ILogger logger,
IMemoryCache cache,
@@ -41,7 +36,7 @@ public abstract class GenericHandler : IHandler, IDisposable
ArrClientFactory arrClientFactory,
ArrQueueIterator arrArrQueueIterator,
DownloadServiceFactory downloadServiceFactory,
- IConfigManager configManager,
+ DataContext dataContext,
EventPublisher eventPublisher
)
{
@@ -52,31 +47,51 @@ public abstract class GenericHandler : IHandler, IDisposable
_arrArrQueueIterator = arrArrQueueIterator;
_downloadServiceFactory = downloadServiceFactory;
_eventPublisher = eventPublisher;
- _generalConfig = configManager.GetConfiguration();
- _downloadClientConfig = configManager.GetConfiguration();
- _sonarrConfig = configManager.GetConfiguration();
- _radarrConfig = configManager.GetConfiguration();
- _lidarrConfig = configManager.GetConfiguration();
+ _dataContext = dataContext;
+ // _generalConfig = configManager.GetConfiguration();
+ // _downloadClientConfigs = configManager.GetConfiguration();
+ // _sonarrConfig = configManager.GetConfiguration();
+ // _radarrConfig = configManager.GetConfiguration();
+ // _lidarrConfig = configManager.GetConfiguration();
}
///
/// Initialize download services based on configuration
///
- protected virtual void InitializeDownloadServices()
+ protected async Task> GetDownloadServices()
{
- // Clear any existing services
- DisposeDownloadServices();
- _downloadServices.Clear();
+ var clients = await _dataContext.DownloadClients
+ .AsNoTracking()
+ .ToListAsync();
+
+ if (clients.Count is 0)
+ {
+ _logger.LogWarning("No download clients configured");
+ return [];
+ }
+
+ var enabledClients = await _dataContext.DownloadClients
+ .Where(c => c.Enabled)
+ .ToListAsync();
+
+ if (enabledClients.Count == 0)
+ {
+ _logger.LogWarning("No enabled download clients available");
+ return [];
+ }
+
+ List downloadServices = [];
// Add all enabled clients
- foreach (var client in _downloadClientConfig.GetEnabledClients())
+ foreach (var client in enabledClients)
{
try
{
var service = _downloadServiceFactory.GetDownloadService(client);
if (service != null)
{
- _downloadServices.Add(service);
+ await service.LoginAsync();
+ downloadServices.Add(service);
_logger.LogDebug("Initialized download client: {name}", client.Name);
}
else
@@ -90,54 +105,52 @@ public abstract class GenericHandler : IHandler, IDisposable
}
}
- if (_downloadServices.Count == 0)
+ if (downloadServices.Count == 0)
{
- _logger.LogWarning("No enabled download clients found");
+ _logger.LogWarning("No valid download clients found");
}
else
{
- _logger.LogDebug("Initialized {count} download clients", _downloadServices.Count);
+ _logger.LogDebug("Initialized {count} download clients", downloadServices.Count);
}
+
+ return downloadServices;
}
- public virtual async Task ExecuteAsync()
+ public async Task ExecuteAsync()
{
- // Initialize download services
- InitializeDownloadServices();
+ ContextProvider.Set(nameof(GeneralConfig), await _dataContext.GeneralConfigs.FirstAsync());
+ ContextProvider.Set(nameof(SonarrConfig), await _dataContext.SonarrConfigs.FirstAsync());
+ ContextProvider.Set(nameof(RadarrConfig), await _dataContext.RadarrConfigs.FirstAsync());
+ ContextProvider.Set(nameof(LidarrConfig), await _dataContext.LidarrConfigs.FirstAsync());
+ ContextProvider.Set(nameof(QueueCleanerConfig), await _dataContext.QueueCleanerConfigs.FirstAsync());
+ ContextProvider.Set(nameof(DownloadCleanerConfig), await _dataContext.DownloadCleanerConfigs.FirstAsync());
- if (_downloadServices.Count == 0)
- {
- _logger.LogWarning("No download clients available, skipping execution");
- return;
- }
-
- // Login to all download services
- foreach (var downloadService in _downloadServices)
- {
- await downloadService.LoginAsync();
- }
-
- await ProcessArrConfigAsync(_sonarrConfig, InstanceType.Sonarr);
- await ProcessArrConfigAsync(_radarrConfig, InstanceType.Radarr);
- await ProcessArrConfigAsync(_lidarrConfig, InstanceType.Lidarr);
+ await ExecuteInternalAsync();
}
+ // {
+ // // Initialize download services
+ // GetDownloadServices();
+ //
+ // if (_downloadServices.Count == 0)
+ // {
+ // _logger.LogWarning("No download clients available, skipping execution");
+ // return;
+ // }
+ //
+ // // Login to all download services
+ // foreach (var downloadService in _downloadServices)
+ // {
+ // await downloadService.LoginAsync();
+ // }
+ //
+ // await ProcessArrConfigAsync(_sonarrConfig, InstanceType.Sonarr);
+ // await ProcessArrConfigAsync(_radarrConfig, InstanceType.Radarr);
+ // await ProcessArrConfigAsync(_lidarrConfig, InstanceType.Lidarr);
+ // }
- public virtual void Dispose()
- {
- DisposeDownloadServices();
- }
+ protected abstract Task ExecuteInternalAsync();
- ///
- /// Dispose all download services
- ///
- protected void DisposeDownloadServices()
- {
- foreach (var service in _downloadServices)
- {
- service.Dispose();
- }
- }
-
protected abstract Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig config);
protected async Task ProcessArrConfigAsync(ArrConfig config, InstanceType instanceType, bool throwOnFailure = false)
@@ -210,27 +223,28 @@ public abstract class GenericHandler : IHandler, IDisposable
protected SearchItem GetRecordSearchItem(InstanceType type, QueueRecord record, bool isPack = false)
{
+ var sonarrConfig = ContextProvider.Get(nameof(SonarrConfig));
return type switch
{
- InstanceType.Sonarr when _sonarrConfig.SearchType is SonarrSearchType.Episode && !isPack => new SonarrSearchItem
+ InstanceType.Sonarr when sonarrConfig.SearchType is SonarrSearchType.Episode && !isPack => new SonarrSearchItem
{
Id = record.EpisodeId,
SeriesId = record.SeriesId,
SearchType = SonarrSearchType.Episode
},
- InstanceType.Sonarr when _sonarrConfig.SearchType is SonarrSearchType.Episode && isPack => new SonarrSearchItem
+ InstanceType.Sonarr when sonarrConfig.SearchType is SonarrSearchType.Episode && isPack => new SonarrSearchItem
{
Id = record.SeasonNumber,
SeriesId = record.SeriesId,
SearchType = SonarrSearchType.Season
},
- InstanceType.Sonarr when _sonarrConfig.SearchType is SonarrSearchType.Season => new SonarrSearchItem
+ InstanceType.Sonarr when sonarrConfig.SearchType is SonarrSearchType.Season => new SonarrSearchItem
{
Id = record.SeasonNumber,
SeriesId = record.SeriesId,
SearchType = SonarrSearchType.Series
},
- InstanceType.Sonarr when _sonarrConfig.SearchType is SonarrSearchType.Series => new SonarrSearchItem
+ InstanceType.Sonarr when sonarrConfig.SearchType is SonarrSearchType.Series => new SonarrSearchItem
{
Id = record.SeriesId
},
diff --git a/code/Infrastructure/Verticals/Notifications/INotificationProvider.cs b/code/Infrastructure/Verticals/Notifications/INotificationProvider.cs
index d1a783f0..02b94709 100644
--- a/code/Infrastructure/Verticals/Notifications/INotificationProvider.cs
+++ b/code/Infrastructure/Verticals/Notifications/INotificationProvider.cs
@@ -5,7 +5,7 @@ namespace Infrastructure.Verticals.Notifications;
public interface INotificationProvider
{
- BaseNotificationConfig Config { get; }
+ NotificationConfig Config { get; }
string Name { get; }
diff --git a/code/Infrastructure/Verticals/Notifications/NotificationProvider.cs b/code/Infrastructure/Verticals/Notifications/NotificationProvider.cs
index 5c3cd201..2065d3b7 100644
--- a/code/Infrastructure/Verticals/Notifications/NotificationProvider.cs
+++ b/code/Infrastructure/Verticals/Notifications/NotificationProvider.cs
@@ -10,7 +10,7 @@ public abstract class NotificationProvider : INotificationProvider
private readonly IConfigManager _configManager;
protected readonly NotificationsConfig _config;
- public abstract BaseNotificationConfig Config { get; }
+ public abstract NotificationConfig Config { get; }
protected NotificationProvider(IConfigManager configManager)
{