diff --git a/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs b/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs
index 90d1283c..e578cacc 100644
--- a/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs
+++ b/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs
@@ -1,5 +1,6 @@
using Common.Configuration.Arr;
using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
namespace Common.Configuration.ContentBlocker;
@@ -10,22 +11,28 @@ public sealed record ContentBlockerConfig : IJobConfig
public bool Enabled { get; init; }
// Trigger configuration
- [ConfigurationKeyName("CRON_EXPRESSION")]
+ [JsonProperty("cron_expression")]
public string CronExpression { get; init; } = "0 */30 * ? * *"; // Default: every 30 minutes
// Privacy settings
- [ConfigurationKeyName("IGNORE_PRIVATE")]
+ [JsonProperty("ignore_private")]
public bool IgnorePrivate { get; init; }
- [ConfigurationKeyName("DELETE_PRIVATE")]
+ [JsonProperty("delete_private")]
public bool DeletePrivate { get; init; }
- [ConfigurationKeyName("IGNORED_DOWNLOADS_PATH")]
+ // TODO
+ [JsonProperty("IGNORED_DOWNLOADS_PATH")]
public string? IgnoredDownloadsPath { get; init; }
// Blocklist settings moved from ArrConfig
+ [JsonProperty("sonarr")]
public BlocklistSettings Sonarr { get; init; } = new();
+
+ [JsonProperty("radarr")]
public BlocklistSettings Radarr { get; init; } = new();
+
+ [JsonProperty("lidarr")]
public BlocklistSettings Lidarr { get; init; } = new();
public void Validate()
diff --git a/code/Common/Configuration/DownloadCleaner/CleanCategory.cs b/code/Common/Configuration/DownloadCleaner/CleanCategory.cs
index 48574cfe..0a659109 100644
--- a/code/Common/Configuration/DownloadCleaner/CleanCategory.cs
+++ b/code/Common/Configuration/DownloadCleaner/CleanCategory.cs
@@ -1,28 +1,31 @@
using Common.Exceptions;
using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
namespace Common.Configuration.DownloadCleaner;
public sealed record CleanCategory : IConfig
{
public required string Name { get; init; }
+
+ // TODO add clean type (category, tag, etc) and make it scoped to a client and rename this to cleanobject or something
///
/// Max ratio before removing a download.
///
- [ConfigurationKeyName("MAX_RATIO")]
+ [JsonProperty("max_ratio")]
public required double MaxRatio { get; init; } = -1;
///
/// Min number of hours to seed before removing a download, if the ratio has been met.
///
- [ConfigurationKeyName("MIN_SEED_TIME")]
- public required double MinSeedTime { get; init; } = 0;
+ [JsonProperty("min_seed_time")]
+ public required double MinSeedTime { get; init; }
///
/// Number of hours to seed before removing a download.
///
- [ConfigurationKeyName("MAX_SEED_TIME")]
+ [JsonProperty("max_seed_time")]
public required double MaxSeedTime { get; init; } = -1;
public void Validate()
diff --git a/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs b/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs
index 42651ee0..3ec34bf3 100644
--- a/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs
+++ b/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs
@@ -1,5 +1,6 @@
using Common.Exceptions;
using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
namespace Common.Configuration.DownloadCleaner;
@@ -10,27 +11,29 @@ public sealed record DownloadCleanerConfig : IJobConfig
public bool Enabled { get; init; }
// Trigger configuration
- [ConfigurationKeyName("CRON_EXPRESSION")]
+ [JsonProperty("cron_expression")]
public string CronExpression { get; init; } = "0 */20 * ? * *"; // Default: every 20 minutes
public List? Categories { get; init; }
- [ConfigurationKeyName("DELETE_PRIVATE")]
+ [JsonProperty("delete_private")]
public bool DeletePrivate { get; init; }
- [ConfigurationKeyName("IGNORED_DOWNLOADS_PATH")]
+ // TODO
+ [JsonProperty("ignored_downloads_path")]
public string? IgnoredDownloadsPath { get; init; }
- [ConfigurationKeyName("UNLINKED_TARGET_CATEGORY")]
+ [JsonProperty("unlinked_target_category")]
public string UnlinkedTargetCategory { get; init; } = "cleanuperr-unlinked";
- [ConfigurationKeyName("UNLINKED_USE_TAG")]
+ [JsonProperty("unlinked_use_tag")]
public bool UnlinkedUseTag { get; init; }
- [ConfigurationKeyName("UNLINKED_IGNORED_ROOT_DIR")]
+ [JsonProperty("unlinked_ignored_root_dir")]
public string UnlinkedIgnoredRootDir { get; init; } = string.Empty;
- [ConfigurationKeyName("UNLINKED_CATEGORIES")]
+ // TODO rename to unlinked objects and add type (category, tag, etc)
+ [JsonProperty("unlinked_categories")]
public List? UnlinkedCategories { get; init; }
public void Validate()
diff --git a/code/Common/Configuration/DownloadClient/ClientConfig.cs b/code/Common/Configuration/DownloadClient/ClientConfig.cs
index d2500b3c..56b8401a 100644
--- a/code/Common/Configuration/DownloadClient/ClientConfig.cs
+++ b/code/Common/Configuration/DownloadClient/ClientConfig.cs
@@ -1,5 +1,6 @@
using Common.Enums;
using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
namespace Common.Configuration.DownloadClient;
@@ -8,10 +9,15 @@ namespace Common.Configuration.DownloadClient;
///
public sealed record ClientConfig
{
+ ///
+ /// Whether this client is enabled
+ ///
+ public bool Enabled { get; init; } = true;
+
///
/// Unique identifier for this client
///
- public string Id { get; init; } = Guid.NewGuid().ToString("N");
+ public Guid Id { get; init; } = Guid.NewGuid();
///
/// Friendly name for this client
@@ -38,32 +44,12 @@ public sealed record ClientConfig
///
public string Password { get; init; } = string.Empty;
- ///
- /// Default category to use
- ///
- public string Category { get; init; } = string.Empty;
-
- ///
- /// Path to download directory
- ///
- public string Path { get; init; } = string.Empty;
-
- ///
- /// Whether this client is enabled
- ///
- public bool Enabled { get; init; } = true;
-
///
/// The base URL path component, used by clients like Transmission and Deluge
///
- [ConfigurationKeyName("URL_BASE")]
+ [JsonProperty("url_base")]
public string UrlBase { get; init; } = string.Empty;
- ///
- /// Use HTTPS protocol
- ///
- public bool UseHttps { get; init; } = false;
-
///
/// The computed full URL for the client
///
@@ -74,7 +60,7 @@ public sealed record ClientConfig
///
public void Validate()
{
- if (string.IsNullOrWhiteSpace(Id))
+ if (Id == Guid.Empty)
{
throw new InvalidOperationException("Client ID cannot be empty");
}
diff --git a/code/Common/Configuration/DownloadClient/DelugeConfig.cs b/code/Common/Configuration/DownloadClient/DelugeConfig.cs
deleted file mode 100644
index 59033a28..00000000
--- a/code/Common/Configuration/DownloadClient/DelugeConfig.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Common.Exceptions;
-using Microsoft.Extensions.Configuration;
-
-namespace Common.Configuration.DownloadClient;
-
-public sealed record DelugeConfig : IConfig
-{
- public const string SectionName = "Deluge";
-
- public Uri? Url { get; init; }
-
- [ConfigurationKeyName("URL_BASE")]
- public string UrlBase { get; init; } = string.Empty;
-
- public string? Password { get; init; }
-
- public void Validate()
- {
- if (Url is null)
- {
- throw new ValidationException($"{nameof(Url)} is empty");
- }
- }
-}
\ No newline at end of file
diff --git a/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs b/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs
index 4c9d0584..48e84145 100644
--- a/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs
+++ b/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs
@@ -12,7 +12,7 @@ public sealed record DownloadClientConfig : IConfig
///
/// The client id
/// The client configuration or null if not found
- public ClientConfig? GetClientConfig(string id)
+ public ClientConfig? GetClientConfig(Guid id)
{
return Clients.FirstOrDefault(c => c.Id == id);
}
@@ -46,7 +46,7 @@ public sealed record DownloadClientConfig : IConfig
// Validate each client configuration
foreach (var client in Clients)
{
- if (string.IsNullOrWhiteSpace(client.Id))
+ if (client.Id == Guid.Empty)
{
throw new InvalidOperationException("Client ID cannot be empty");
}
diff --git a/code/Common/Configuration/DownloadClient/QBitConfig.cs b/code/Common/Configuration/DownloadClient/QBitConfig.cs
deleted file mode 100644
index 7de0fafb..00000000
--- a/code/Common/Configuration/DownloadClient/QBitConfig.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Common.Exceptions;
-using Microsoft.Extensions.Configuration;
-
-namespace Common.Configuration.DownloadClient;
-
-public sealed class QBitConfig : IConfig
-{
- public const string SectionName = "qBittorrent";
-
- public Uri? Url { get; init; }
-
- [ConfigurationKeyName("URL_BASE")]
- public string UrlBase { get; init; } = string.Empty;
-
- public string? Username { get; init; }
-
- public string? Password { get; init; }
-
- public void Validate()
- {
- if (Url is null)
- {
- throw new ValidationException($"{nameof(Url)} is empty");
- }
- }
-}
\ No newline at end of file
diff --git a/code/Common/Configuration/DownloadClient/TransmissionConfig.cs b/code/Common/Configuration/DownloadClient/TransmissionConfig.cs
deleted file mode 100644
index 4d30b626..00000000
--- a/code/Common/Configuration/DownloadClient/TransmissionConfig.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Common.Exceptions;
-using Microsoft.Extensions.Configuration;
-
-namespace Common.Configuration.DownloadClient;
-
-public record TransmissionConfig : IConfig
-{
- public const string SectionName = "Transmission";
-
- public Uri? Url { get; init; }
-
- [ConfigurationKeyName("URL_BASE")]
- public string UrlBase { get; init; } = "transmission";
-
- public string? Username { get; init; }
-
- public string? Password { get; init; }
-
- public void Validate()
- {
- if (Url is null)
- {
- throw new ValidationException($"{nameof(Url)} is empty");
- }
- }
-}
\ No newline at end of file
diff --git a/code/Executable/Controllers/DownloadClientsController.cs b/code/Executable/Controllers/DownloadClientsController.cs
index c2ce60ed..b3a1370c 100644
--- a/code/Executable/Controllers/DownloadClientsController.cs
+++ b/code/Executable/Controllers/DownloadClientsController.cs
@@ -57,8 +57,8 @@ public class DownloadClientsController : ControllerBase
///
/// Gets a specific download client by ID
///
- [HttpGet("{id}")]
- public async Task GetClient(string id)
+ [HttpGet("{id:guid}")]
+ public async Task GetClient(Guid id)
{
try
{
@@ -133,8 +133,8 @@ public class DownloadClientsController : ControllerBase
///
/// Updates an existing download client
///
- [HttpPut("{id}")]
- public async Task UpdateClient(string id, [FromBody] ClientConfig clientConfig)
+ [HttpPut("{id:guid}")]
+ public async Task UpdateClient(Guid id, [FromBody] ClientConfig clientConfig)
{
try
{
@@ -184,8 +184,8 @@ public class DownloadClientsController : ControllerBase
///
/// Deletes a download client
///
- [HttpDelete("{id}")]
- public async Task DeleteClient(string id)
+ [HttpDelete("{id:guid}")]
+ public async Task DeleteClient(Guid id)
{
try
{
@@ -226,8 +226,8 @@ public class DownloadClientsController : ControllerBase
///
/// Tests connection to a download client
///
- [HttpPost("{id}/test")]
- public async Task TestConnection(string id)
+ [HttpPost("{id:guid}/test")]
+ public async Task TestConnection(Guid id)
{
try
{
diff --git a/code/Executable/Controllers/HealthCheckController.cs b/code/Executable/Controllers/HealthCheckController.cs
index e883f6e2..caf7f47c 100644
--- a/code/Executable/Controllers/HealthCheckController.cs
+++ b/code/Executable/Controllers/HealthCheckController.cs
@@ -45,8 +45,8 @@ public class HealthCheckController : ControllerBase
///
/// Gets the health status of a specific download client
///
- [HttpGet("{id}")]
- public IActionResult GetClientHealth(string id)
+ [HttpGet("{id:guid}")]
+ public IActionResult GetClientHealth(Guid id)
{
try
{
@@ -86,8 +86,8 @@ public class HealthCheckController : ControllerBase
///
/// Triggers a health check for a specific download client
///
- [HttpPost("check/{id}")]
- public async Task CheckClientHealth(string id)
+ [HttpPost("check/{id:guid}")]
+ public async Task CheckClientHealth(Guid id)
{
try
{
diff --git a/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs b/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs
index fa575dc0..48a8dfdf 100644
--- a/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs
+++ b/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs
@@ -24,6 +24,7 @@ public class HealthCheckServiceFixture : IDisposable
ConfigManager = Substitute.For();
ClientFactory = Substitute.For();
MockClient = Substitute.For();
+ Guid clientId = Guid.NewGuid();
// Set up test download client config
DownloadClientConfig = new DownloadClientConfig
@@ -32,7 +33,7 @@ public class HealthCheckServiceFixture : IDisposable
{
new()
{
- Id = "qbit1",
+ Id = clientId,
Name = "Test QBittorrent",
Type = DownloadClientType.QBittorrent,
Enabled = true,
@@ -41,7 +42,7 @@ public class HealthCheckServiceFixture : IDisposable
},
new()
{
- Id = "transmission1",
+ Id = Guid.NewGuid(),
Name = "Test Transmission",
Type = DownloadClientType.Transmission,
Enabled = true,
@@ -50,7 +51,7 @@ public class HealthCheckServiceFixture : IDisposable
},
new()
{
- Id = "disabled1",
+ Id = Guid.NewGuid(),
Name = "Disabled Client",
Type = DownloadClientType.QBittorrent,
Enabled = false,
@@ -59,8 +60,8 @@ public class HealthCheckServiceFixture : IDisposable
};
// Set up the mock client factory
- ClientFactory.GetClient(Arg.Any()).Returns(MockClient);
- MockClient.GetClientId().Returns("qbit1");
+ ClientFactory.GetClient(Arg.Any()).Returns(MockClient);
+ MockClient.GetClientId().Returns(clientId);
// Set up mock config manager
ConfigManager.GetDownloadClientConfigAsync().Returns(DownloadClientConfig);
@@ -71,13 +72,13 @@ public class HealthCheckServiceFixture : IDisposable
return new HealthCheckService(Logger, ConfigManager, ClientFactory);
}
- public void SetupHealthyClient(string clientId)
+ public void SetupHealthyClient(Guid clientId)
{
// Setup a client that will successfully login
MockClient.LoginAsync().Returns(Task.CompletedTask);
}
- public void SetupUnhealthyClient(string clientId, string errorMessage = "Failed to connect")
+ public void SetupUnhealthyClient(Guid clientId, string errorMessage = "Failed to connect")
{
// Setup a client that will fail to login
MockClient.LoginAsync().Throws(new Exception(errorMessage));
diff --git a/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs b/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs
index 2b90f560..df448f71 100644
--- a/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs
+++ b/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs
@@ -19,15 +19,15 @@ public class HealthCheckServiceTests : IClassFixture
{
// Arrange
var sut = _fixture.CreateSut();
- _fixture.SetupHealthyClient("qbit1");
+ _fixture.SetupHealthyClient(new Guid("00000000-0000-0000-0000-000000000001"));
// Act
- var result = await sut.CheckClientHealthAsync("qbit1");
+ var result = await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001"));
// Assert
result.ShouldSatisfyAllConditions(
() => result.IsHealthy.ShouldBeTrue(),
- () => result.ClientId.ShouldBe("qbit1"),
+ () => result.ClientId.ShouldBe(new Guid("00000000-0000-0000-0000-000000000001")),
() => result.ErrorMessage.ShouldBeNull(),
() => result.LastChecked.ShouldBeInRange(DateTime.UtcNow.AddSeconds(-10), DateTime.UtcNow)
);
@@ -38,15 +38,15 @@ public class HealthCheckServiceTests : IClassFixture
{
// Arrange
var sut = _fixture.CreateSut();
- _fixture.SetupUnhealthyClient("qbit1", "Connection refused");
+ _fixture.SetupUnhealthyClient(new Guid("00000000-0000-0000-0000-000000000001"), "Connection refused");
// Act
- var result = await sut.CheckClientHealthAsync("qbit1");
+ var result = await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001"));
// Assert
result.ShouldSatisfyAllConditions(
() => result.IsHealthy.ShouldBeFalse(),
- () => result.ClientId.ShouldBe("qbit1"),
+ () => result.ClientId.ShouldBe(new Guid("00000000-0000-0000-0000-000000000001")),
() => result.ErrorMessage.ShouldContain("Connection refused"),
() => result.LastChecked.ShouldBeInRange(DateTime.UtcNow.AddSeconds(-10), DateTime.UtcNow)
);
@@ -64,12 +64,12 @@ public class HealthCheckServiceTests : IClassFixture
);
// Act
- var result = await sut.CheckClientHealthAsync("non-existent");
+ var result = await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000010"));
// Assert
result.ShouldSatisfyAllConditions(
() => result.IsHealthy.ShouldBeFalse(),
- () => result.ClientId.ShouldBe("non-existent"),
+ () => result.ClientId.ShouldBe(new Guid("00000000-0000-0000-0000-000000000010")),
() => result.ErrorMessage.ShouldContain("not found"),
() => result.LastChecked.ShouldBeInRange(DateTime.UtcNow.AddSeconds(-10), DateTime.UtcNow)
);
@@ -80,18 +80,18 @@ public class HealthCheckServiceTests : IClassFixture
{
// Arrange
var sut = _fixture.CreateSut();
- _fixture.SetupHealthyClient("qbit1");
- _fixture.SetupUnhealthyClient("transmission1");
+ _fixture.SetupHealthyClient(new Guid("00000000-0000-0000-0000-000000000001"));
+ _fixture.SetupUnhealthyClient(new Guid("00000000-0000-0000-0000-000000000002"));
// Act
var results = await sut.CheckAllClientsHealthAsync();
// Assert
results.Count.ShouldBe(2); // Only enabled clients
- results.Keys.ShouldContain("qbit1");
- results.Keys.ShouldContain("transmission1");
- results["qbit1"].IsHealthy.ShouldBeTrue();
- results["transmission1"].IsHealthy.ShouldBeFalse();
+ results.Keys.ShouldContain(new Guid("00000000-0000-0000-0000-000000000001"));
+ results.Keys.ShouldContain(new Guid("00000000-0000-0000-0000-000000000002"));
+ results[new Guid("00000000-0000-0000-0000-000000000001")].IsHealthy.ShouldBeTrue();
+ results[new Guid("00000000-0000-0000-0000-000000000002")].IsHealthy.ShouldBeFalse();
}
[Fact]
@@ -99,23 +99,23 @@ public class HealthCheckServiceTests : IClassFixture
{
// Arrange
var sut = _fixture.CreateSut();
- _fixture.SetupHealthyClient("qbit1");
+ _fixture.SetupHealthyClient(new Guid("00000000-0000-0000-0000-000000000001"));
ClientHealthChangedEventArgs? capturedArgs = null;
sut.ClientHealthChanged += (_, args) => capturedArgs = args;
// Act - first check establishes initial state
- var firstResult = await sut.CheckClientHealthAsync("qbit1");
+ var firstResult = await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001"));
// Setup client to be unhealthy for second check
- _fixture.SetupUnhealthyClient("qbit1");
+ _fixture.SetupUnhealthyClient(new Guid("00000000-0000-0000-0000-000000000001"));
// Act - second check changes state
- var secondResult = await sut.CheckClientHealthAsync("qbit1");
+ var secondResult = await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001"));
// Assert
capturedArgs.ShouldNotBeNull();
- capturedArgs.ClientId.ShouldBe("qbit1");
+ capturedArgs.ClientId.ShouldBe(new Guid("00000000-0000-0000-0000-000000000001"));
capturedArgs.Status.IsHealthy.ShouldBeFalse();
capturedArgs.IsDegraded.ShouldBeTrue();
capturedArgs.IsRecovered.ShouldBeFalse();
@@ -126,18 +126,18 @@ public class HealthCheckServiceTests : IClassFixture
{
// Arrange
var sut = _fixture.CreateSut();
- _fixture.SetupHealthyClient("qbit1");
+ _fixture.SetupHealthyClient(new Guid("00000000-0000-0000-0000-000000000001"));
// Perform a check to cache the status
- await sut.CheckClientHealthAsync("qbit1");
+ await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001"));
// Act
- var result = sut.GetClientHealth("qbit1");
+ var result = sut.GetClientHealth(new Guid("00000000-0000-0000-0000-000000000001"));
// Assert
result.ShouldNotBeNull();
result.IsHealthy.ShouldBeTrue();
- result.ClientId.ShouldBe("qbit1");
+ result.ClientId.ShouldBe(new Guid("00000000-0000-0000-0000-000000000001"));
}
[Fact]
@@ -147,7 +147,7 @@ public class HealthCheckServiceTests : IClassFixture
var sut = _fixture.CreateSut();
// Act
- var result = sut.GetClientHealth("qbit1");
+ var result = sut.GetClientHealth(new Guid("00000000-0000-0000-0000-000000000001"));
// Assert
result.ShouldBeNull();
@@ -158,21 +158,21 @@ public class HealthCheckServiceTests : IClassFixture
{
// Arrange
var sut = _fixture.CreateSut();
- _fixture.SetupHealthyClient("qbit1");
- _fixture.SetupUnhealthyClient("transmission1");
+ _fixture.SetupHealthyClient(new Guid("00000000-0000-0000-0000-000000000001"));
+ _fixture.SetupUnhealthyClient(new Guid("00000000-0000-0000-0000-000000000002"));
// Perform checks to cache statuses
- await sut.CheckClientHealthAsync("qbit1");
- await sut.CheckClientHealthAsync("transmission1");
+ await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001"));
+ await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000002"));
// Act
var results = sut.GetAllClientHealth();
// Assert
results.Count.ShouldBe(2);
- results.Keys.ShouldContain("qbit1");
- results.Keys.ShouldContain("transmission1");
- results["qbit1"].IsHealthy.ShouldBeTrue();
- results["transmission1"].IsHealthy.ShouldBeFalse();
+ results.Keys.ShouldContain(new Guid("00000000-0000-0000-0000-000000000001"));
+ results.Keys.ShouldContain(new Guid("00000000-0000-0000-0000-000000000002"));
+ results[new Guid("00000000-0000-0000-0000-000000000001")].IsHealthy.ShouldBeTrue();
+ results[new Guid("00000000-0000-0000-0000-000000000002")].IsHealthy.ShouldBeFalse();
}
}
diff --git a/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs b/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs
index 43a59ba6..99dc7037 100644
--- a/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs
+++ b/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs
@@ -34,7 +34,7 @@ public class DynamicHttpClientProviderFixture : IDisposable
{
return new ClientConfig
{
- Id = "qbit-test",
+ Id = Guid.NewGuid(),
Name = "QBit Test",
Type = DownloadClientType.QBittorrent,
Enabled = true,
@@ -48,7 +48,7 @@ public class DynamicHttpClientProviderFixture : IDisposable
{
return new ClientConfig
{
- Id = "transmission-test",
+ Id = Guid.NewGuid(),
Name = "Transmission Test",
Type = DownloadClientType.Transmission,
Enabled = true,
@@ -63,7 +63,7 @@ public class DynamicHttpClientProviderFixture : IDisposable
{
return new ClientConfig
{
- Id = "deluge-test",
+ Id = Guid.NewGuid(),
Name = "Deluge Test",
Type = DownloadClientType.Deluge,
Enabled = true,
diff --git a/code/Infrastructure/Configuration/JsonConfigurationProvider.cs b/code/Infrastructure/Configuration/JsonConfigurationProvider.cs
index d344fe38..99250b12 100644
--- a/code/Infrastructure/Configuration/JsonConfigurationProvider.cs
+++ b/code/Infrastructure/Configuration/JsonConfigurationProvider.cs
@@ -79,7 +79,7 @@ public class JsonConfigurationProvider
if (!File.Exists(fullPath))
{
- _logger.LogWarning("Configuration file does not exist: {file}", fullPath);
+ _logger.LogDebug("Configuration file does not exist: {file}", fullPath);
return new T();
}
diff --git a/code/Infrastructure/Health/ClientHealthChangedEventArgs.cs b/code/Infrastructure/Health/ClientHealthChangedEventArgs.cs
index 1f944a7b..30e9892e 100644
--- a/code/Infrastructure/Health/ClientHealthChangedEventArgs.cs
+++ b/code/Infrastructure/Health/ClientHealthChangedEventArgs.cs
@@ -8,7 +8,7 @@ public class ClientHealthChangedEventArgs : EventArgs
///
/// Gets the client ID
///
- public string ClientId { get; }
+ public Guid ClientId { get; }
///
/// Gets the health status
@@ -31,7 +31,7 @@ public class ClientHealthChangedEventArgs : EventArgs
/// The client ID
/// The current health status
/// The previous health status, if any
- public ClientHealthChangedEventArgs(string clientId, HealthStatus status, HealthStatus? previousStatus)
+ public ClientHealthChangedEventArgs(Guid clientId, HealthStatus status, HealthStatus? previousStatus)
{
ClientId = clientId;
Status = status;
diff --git a/code/Infrastructure/Health/HealthCheckService.cs b/code/Infrastructure/Health/HealthCheckService.cs
index 13417f9a..152a2482 100644
--- a/code/Infrastructure/Health/HealthCheckService.cs
+++ b/code/Infrastructure/Health/HealthCheckService.cs
@@ -14,7 +14,7 @@ public class HealthCheckService : IHealthCheckService
private readonly ILogger _logger;
private readonly IConfigManager _configManager;
private readonly IDownloadClientFactory _clientFactory;
- private readonly Dictionary _healthStatuses = new();
+ private readonly Dictionary _healthStatuses = new();
private readonly object _lockObject = new();
///
@@ -39,7 +39,7 @@ public class HealthCheckService : IHealthCheckService
}
///
- public async Task CheckClientHealthAsync(string clientId)
+ public async Task CheckClientHealthAsync(Guid clientId)
{
_logger.LogDebug("Checking health for client {clientId}", clientId);
@@ -128,7 +128,7 @@ public class HealthCheckService : IHealthCheckService
}
///
- public async Task> CheckAllClientsHealthAsync()
+ public async Task> CheckAllClientsHealthAsync()
{
_logger.LogDebug("Checking health for all enabled clients");
@@ -139,11 +139,11 @@ public class HealthCheckService : IHealthCheckService
if (config == null)
{
_logger.LogWarning("Download client configuration not found");
- return new Dictionary();
+ return new Dictionary();
}
var enabledClients = config.GetEnabledClients();
- var results = new Dictionary();
+ var results = new Dictionary();
// Check health of each enabled client
foreach (var clientConfig in enabledClients)
@@ -157,12 +157,12 @@ public class HealthCheckService : IHealthCheckService
catch (Exception ex)
{
_logger.LogError(ex, "Error checking health for all clients");
- return new Dictionary();
+ return new Dictionary();
}
}
///
- public HealthStatus? GetClientHealth(string clientId)
+ public HealthStatus? GetClientHealth(Guid clientId)
{
lock (_lockObject)
{
@@ -171,15 +171,15 @@ public class HealthCheckService : IHealthCheckService
}
///
- public IDictionary GetAllClientHealth()
+ public IDictionary GetAllClientHealth()
{
lock (_lockObject)
{
- return new Dictionary(_healthStatuses);
+ return new Dictionary(_healthStatuses);
}
}
- private async Task GetClientConfigAsync(string clientId)
+ private async Task GetClientConfigAsync(Guid clientId)
{
var config = await _configManager.GetDownloadClientConfigAsync();
return config?.GetClientConfig(clientId);
diff --git a/code/Infrastructure/Health/HealthStatus.cs b/code/Infrastructure/Health/HealthStatus.cs
index 7f36a63b..5a5c2d94 100644
--- a/code/Infrastructure/Health/HealthStatus.cs
+++ b/code/Infrastructure/Health/HealthStatus.cs
@@ -28,7 +28,7 @@ public class HealthStatus
///
/// Gets or sets the client ID
///
- public string ClientId { get; set; } = string.Empty;
+ public Guid ClientId { get; set; } = Guid.Empty;
///
/// Gets or sets the client name
diff --git a/code/Infrastructure/Health/IHealthCheckService.cs b/code/Infrastructure/Health/IHealthCheckService.cs
index 1b334385..e799747c 100644
--- a/code/Infrastructure/Health/IHealthCheckService.cs
+++ b/code/Infrastructure/Health/IHealthCheckService.cs
@@ -15,24 +15,24 @@ public interface IHealthCheckService
///
/// The client ID to check
/// The health status of the client
- Task CheckClientHealthAsync(string clientId);
+ Task CheckClientHealthAsync(Guid clientId);
///
/// Checks the health of all enabled clients
///
/// A dictionary of client IDs to health statuses
- Task> CheckAllClientsHealthAsync();
+ Task> CheckAllClientsHealthAsync();
///
/// Gets the current health status of a client
///
/// The client ID
/// The current health status, or null if the client hasn't been checked
- HealthStatus? GetClientHealth(string clientId);
+ HealthStatus? GetClientHealth(Guid clientId);
///
/// Gets the current health status of all clients that have been checked
///
/// A dictionary of client IDs to health statuses
- IDictionary GetAllClientHealth();
+ IDictionary GetAllClientHealth();
}
diff --git a/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs
index a58c3b1f..9eeff067 100644
--- a/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs
+++ b/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs
@@ -70,7 +70,7 @@ public abstract class DownloadService : IDownloadService
}
///
- public string GetClientId()
+ public Guid GetClientId()
{
return _clientConfig.Id;
}
diff --git a/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs b/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs
index bc94d152..524744ff 100644
--- a/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs
+++ b/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs
@@ -33,9 +33,9 @@ public sealed class DownloadServiceFactory
///
/// The client ID to create a service for
/// An implementation of IDownloadService or null if the client is not available
- public IDownloadService? GetDownloadService(string clientId)
+ public IDownloadService? GetDownloadService(Guid clientId)
{
- var config = _configManager.GetDownloadClientConfigAsync().GetAwaiter().GetResult();
+ var config = _configManager.GetDownloadClientConfig();
if (config == null)
{
diff --git a/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs b/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs
index 7c06831a..a337570b 100644
--- a/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs
+++ b/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs
@@ -25,7 +25,7 @@ public class DownloadClientFactory : IDownloadClientFactory
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IConfigManager _configManager;
- private readonly ConcurrentDictionary _clients = new();
+ private readonly ConcurrentDictionary _clients = new();
public DownloadClientFactory(
ILogger logger,
@@ -38,9 +38,9 @@ public class DownloadClientFactory : IDownloadClientFactory
}
///
- public IDownloadService GetClient(string clientId)
+ public IDownloadService GetClient(Guid clientId)
{
- if (string.IsNullOrWhiteSpace(clientId))
+ if (clientId == Guid.Empty)
{
throw new ArgumentException("Client ID cannot be empty", nameof(clientId));
}
@@ -73,7 +73,7 @@ public class DownloadClientFactory : IDownloadClientFactory
}
///
- public void RefreshClient(string clientId)
+ public void RefreshClient(Guid clientId)
{
if (_clients.TryRemove(clientId, out var service))
{
@@ -100,7 +100,7 @@ public class DownloadClientFactory : IDownloadClientFactory
}
}
- private IDownloadService CreateClient(string clientId)
+ private IDownloadService CreateClient(Guid clientId)
{
var downloadClientConfig = _configManager.GetConfiguration("downloadclients.json")
?? new DownloadClientConfig();
diff --git a/code/Infrastructure/Verticals/DownloadClient/Factory/IDownloadClientFactory.cs b/code/Infrastructure/Verticals/DownloadClient/Factory/IDownloadClientFactory.cs
index 64997a19..a6e0c201 100644
--- a/code/Infrastructure/Verticals/DownloadClient/Factory/IDownloadClientFactory.cs
+++ b/code/Infrastructure/Verticals/DownloadClient/Factory/IDownloadClientFactory.cs
@@ -12,7 +12,7 @@ public interface IDownloadClientFactory
///
/// The client ID
/// The download service for the specified client
- IDownloadService GetClient(string clientId);
+ IDownloadService GetClient(Guid clientId);
///
/// Gets all enabled download clients
@@ -31,7 +31,7 @@ public interface IDownloadClientFactory
/// Refreshes a specific client instance (disposes and recreates)
///
/// The client ID to refresh
- void RefreshClient(string clientId);
+ void RefreshClient(Guid clientId);
///
/// Refreshes all client instances (disposes and recreates)
diff --git a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs
index 721eb37c..36fa9af2 100644
--- a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs
+++ b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs
@@ -13,7 +13,7 @@ public interface IDownloadService : IDisposable
/// Gets the unique identifier for this download client
///
/// The client ID
- string GetClientId();
+ Guid GetClientId();
///
/// Initializes the download service with client-specific configuration