From f51973bb7b2923d9e76543700e4497d89e3d2b4e Mon Sep 17 00:00:00 2001 From: Flaminel Date: Fri, 6 Mar 2026 17:53:04 +0200 Subject: [PATCH] Remove the known malware feature (#489) --- .../static/known_malware_file_name_patterns | 2 - .../UpdateMalwareBlockerConfigRequest.cs | 3 - .../Cleanuparr.Domain/Enums/DeleteReason.cs | 1 - .../DownloadRemover/QueueItemRemoverTests.cs | 1 - .../Features/Jobs/MalwareBlockerTests.cs | 53 +- .../TestHelpers/TestDataContextFactory.cs | 1 - .../MalwareBlocker/BlocklistProviderTests.cs | 28 - .../NotificationPublisherTests.cs | 4 +- .../DownloadClient/Deluge/DelugeServiceCB.cs | 8 - .../QBittorrent/QBitServiceCB.cs | 11 +- .../RTorrent/RTorrentServiceCB.cs | 8 - .../Transmission/TransmissionServiceCB.cs | 13 +- .../UTorrent/UTorrentServiceCB.cs | 9 - .../Features/Jobs/MalwareBlocker.cs | 18 +- .../MalwareBlocker/BlocklistProvider.cs | 58 +- .../MalwareBlocker/FilenameEvaluator.cs | 10 - .../MalwareBlocker/IBlocklistProvider.cs | 2 - .../MalwareBlocker/IFilenameEvaluator.cs | 2 - .../Helpers/CacheKeys.cs | 2 - ...44520_RemoveKnownMalwareOption.Designer.cs | 1300 +++++++++++++++++ ...20260306144520_RemoveKnownMalwareOption.cs | 29 + .../Data/DataContextModelSnapshot.cs | 4 - .../MalwareBlocker/ContentBlockerConfig.cs | 2 - .../core/services/documentation.service.ts | 1 - .../malware-blocker.component.html | 3 - .../malware-blocker.component.ts | 4 - .../models/malware-blocker-config.model.ts | 1 - docs/docs/2_features.mdx | 1 - .../configuration/malware-blocker/index.mdx | 18 - 29 files changed, 1385 insertions(+), 212 deletions(-) delete mode 100644 Cloudflare/static/known_malware_file_name_patterns create mode 100644 code/backend/Cleanuparr.Persistence/Migrations/Data/20260306144520_RemoveKnownMalwareOption.Designer.cs create mode 100644 code/backend/Cleanuparr.Persistence/Migrations/Data/20260306144520_RemoveKnownMalwareOption.cs diff --git a/Cloudflare/static/known_malware_file_name_patterns b/Cloudflare/static/known_malware_file_name_patterns deleted file mode 100644 index 29e4ce0d..00000000 --- a/Cloudflare/static/known_malware_file_name_patterns +++ /dev/null @@ -1,2 +0,0 @@ -thepirateheaven.org -RARBG.work \ No newline at end of file diff --git a/code/backend/Cleanuparr.Api/Features/MalwareBlocker/Contracts/Requests/UpdateMalwareBlockerConfigRequest.cs b/code/backend/Cleanuparr.Api/Features/MalwareBlocker/Contracts/Requests/UpdateMalwareBlockerConfigRequest.cs index 77d848a8..adb832b9 100644 --- a/code/backend/Cleanuparr.Api/Features/MalwareBlocker/Contracts/Requests/UpdateMalwareBlockerConfigRequest.cs +++ b/code/backend/Cleanuparr.Api/Features/MalwareBlocker/Contracts/Requests/UpdateMalwareBlockerConfigRequest.cs @@ -16,8 +16,6 @@ public sealed record UpdateMalwareBlockerConfigRequest public bool DeletePrivate { get; init; } - public bool DeleteKnownMalware { get; init; } - public BlocklistSettings Sonarr { get; init; } = new(); public BlocklistSettings Radarr { get; init; } = new(); @@ -37,7 +35,6 @@ public sealed record UpdateMalwareBlockerConfigRequest config.UseAdvancedScheduling = UseAdvancedScheduling; config.IgnorePrivate = IgnorePrivate; config.DeletePrivate = DeletePrivate; - config.DeleteKnownMalware = DeleteKnownMalware; config.Sonarr = Sonarr; config.Radarr = Radarr; config.Lidarr = Lidarr; diff --git a/code/backend/Cleanuparr.Domain/Enums/DeleteReason.cs b/code/backend/Cleanuparr.Domain/Enums/DeleteReason.cs index caed2a14..af3a9394 100644 --- a/code/backend/Cleanuparr.Domain/Enums/DeleteReason.cs +++ b/code/backend/Cleanuparr.Domain/Enums/DeleteReason.cs @@ -11,5 +11,4 @@ public enum DeleteReason AllFilesSkipped, AllFilesSkippedByQBit, AllFilesBlocked, - MalwareFileFound, } \ No newline at end of file diff --git a/code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs b/code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs index efe2bc29..9872cba1 100644 --- a/code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs +++ b/code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs @@ -377,7 +377,6 @@ public class QueueItemRemoverTests : IDisposable [InlineData(DeleteReason.SlowSpeed)] [InlineData(DeleteReason.SlowTime)] [InlineData(DeleteReason.DownloadingMetadata)] - [InlineData(DeleteReason.MalwareFileFound)] public async Task RemoveQueueItemAsync_PassesCorrectDeleteReason(DeleteReason deleteReason) { // Arrange diff --git a/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/MalwareBlockerTests.cs b/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/MalwareBlockerTests.cs index 2d0c563f..89f45954 100644 --- a/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/MalwareBlockerTests.cs +++ b/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/MalwareBlockerTests.cs @@ -159,24 +159,21 @@ public class MalwareBlockerTests : IDisposable _fixture.ArrClientFactory.Verify(x => x.GetClient(InstanceType.Sonarr, It.IsAny()), Times.Once); } - [Fact] - public async Task ExecuteInternalAsync_WhenDeleteKnownMalwareEnabled_ProcessesAllArrs() + [Theory] + [InlineData(InstanceType.Radarr)] + [InlineData(InstanceType.Lidarr)] + [InlineData(InstanceType.Readarr)] + [InlineData(InstanceType.Whisparr)] + public async Task ExecuteInternalAsync_WhenArrTypeEnabled_ProcessesCorrectInstances(InstanceType instanceType) { // Arrange TestDataContextFactory.AddDownloadClient(_fixture.DataContext); - - var contentBlockerConfig = _fixture.DataContext.ContentBlockerConfigs.First(); - contentBlockerConfig.DeleteKnownMalware = true; - // Need at least one blocklist enabled for processing to occur - contentBlockerConfig.Sonarr = new BlocklistSettings { Enabled = true }; - _fixture.DataContext.SaveChanges(); - - TestDataContextFactory.AddSonarrInstance(_fixture.DataContext); - TestDataContextFactory.AddRadarrInstance(_fixture.DataContext); + EnableBlocklist(instanceType); + AddArrInstance(instanceType); var mockArrClient = new Mock(); _fixture.ArrClientFactory - .Setup(x => x.GetClient(It.IsAny(), It.IsAny())) + .Setup(x => x.GetClient(instanceType, It.IsAny())) .Returns(mockArrClient.Object); _fixture.ArrQueueIterator @@ -192,9 +189,8 @@ public class MalwareBlockerTests : IDisposable // Act await sut.ExecuteAsync(); - // Assert - Sonarr and Radarr processed because DeleteKnownMalware is true - _fixture.ArrClientFactory.Verify(x => x.GetClient(InstanceType.Sonarr, It.IsAny()), Times.Once); - _fixture.ArrClientFactory.Verify(x => x.GetClient(InstanceType.Radarr, It.IsAny()), Times.Once); + // Assert + _fixture.ArrClientFactory.Verify(x => x.GetClient(instanceType, It.IsAny()), Times.Once); } #endregion @@ -605,5 +601,32 @@ public class MalwareBlockerTests : IDisposable _fixture.DataContext.SaveChanges(); } + private void EnableBlocklist(InstanceType instanceType) + { + var config = _fixture.DataContext.ContentBlockerConfigs.First(); + var settings = new BlocklistSettings { Enabled = true }; + switch (instanceType) + { + case InstanceType.Radarr: config.Radarr = settings; break; + case InstanceType.Lidarr: config.Lidarr = settings; break; + case InstanceType.Readarr: config.Readarr = settings; break; + case InstanceType.Whisparr: config.Whisparr = settings; break; + default: throw new ArgumentOutOfRangeException(nameof(instanceType)); + } + _fixture.DataContext.SaveChanges(); + } + + private void AddArrInstance(InstanceType instanceType) + { + switch (instanceType) + { + case InstanceType.Radarr: TestDataContextFactory.AddRadarrInstance(_fixture.DataContext); break; + case InstanceType.Lidarr: TestDataContextFactory.AddLidarrInstance(_fixture.DataContext); break; + case InstanceType.Readarr: TestDataContextFactory.AddReadarrInstance(_fixture.DataContext); break; + case InstanceType.Whisparr: TestDataContextFactory.AddWhisparrInstance(_fixture.DataContext); break; + default: throw new ArgumentOutOfRangeException(nameof(instanceType)); + } + } + #endregion } diff --git a/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/TestHelpers/TestDataContextFactory.cs b/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/TestHelpers/TestDataContextFactory.cs index d45bc067..f43ba633 100644 --- a/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/TestHelpers/TestDataContextFactory.cs +++ b/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/TestHelpers/TestDataContextFactory.cs @@ -75,7 +75,6 @@ public static class TestDataContextFactory { Id = Guid.NewGuid(), IgnoredDownloads = [], - DeleteKnownMalware = false, DeletePrivate = false, Sonarr = new BlocklistSettings { Enabled = false }, Radarr = new BlocklistSettings { Enabled = false }, diff --git a/code/backend/Cleanuparr.Infrastructure.Tests/Features/MalwareBlocker/BlocklistProviderTests.cs b/code/backend/Cleanuparr.Infrastructure.Tests/Features/MalwareBlocker/BlocklistProviderTests.cs index 08c2530d..c476be22 100644 --- a/code/backend/Cleanuparr.Infrastructure.Tests/Features/MalwareBlocker/BlocklistProviderTests.cs +++ b/code/backend/Cleanuparr.Infrastructure.Tests/Features/MalwareBlocker/BlocklistProviderTests.cs @@ -118,34 +118,6 @@ public class BlocklistProviderTests : IDisposable result.Count.ShouldBe(2); } - [Fact] - public void GetMalwarePatterns_NotInCache_ReturnsEmptyBag() - { - // Act - var result = _provider.GetMalwarePatterns(); - - // Assert - result.ShouldNotBeNull(); - result.ShouldBeEmpty(); - } - - [Fact] - public void GetMalwarePatterns_InCache_ReturnsCachedPatterns() - { - // Arrange - var patterns = new ConcurrentBag { "known_malware.exe", "trojan*", "virus.dll" }; - _cache.Set(CacheKeys.KnownMalwarePatterns(), patterns); - - // Act - var result = _provider.GetMalwarePatterns(); - - // Assert - result.Count.ShouldBe(3); - result.ShouldContain("known_malware.exe"); - result.ShouldContain("trojan*"); - result.ShouldContain("virus.dll"); - } - [Theory] [InlineData(InstanceType.Sonarr)] [InlineData(InstanceType.Radarr)] diff --git a/code/backend/Cleanuparr.Infrastructure.Tests/Features/Notifications/NotificationPublisherTests.cs b/code/backend/Cleanuparr.Infrastructure.Tests/Features/Notifications/NotificationPublisherTests.cs index efa8a330..84b78512 100644 --- a/code/backend/Cleanuparr.Infrastructure.Tests/Features/Notifications/NotificationPublisherTests.cs +++ b/code/backend/Cleanuparr.Infrastructure.Tests/Features/Notifications/NotificationPublisherTests.cs @@ -271,12 +271,12 @@ public class NotificationPublisherTests .Returns(providerMock.Object); // Act - await _publisher.NotifyQueueItemDeleted(false, DeleteReason.MalwareFileFound); + await _publisher.NotifyQueueItemDeleted(false, DeleteReason.AllFilesBlocked); // Assert providerMock.Verify(p => p.SendNotificationAsync(It.Is( c => c.Data["Removed from client?"] == "False" && - c.Data["Reason"] == "MalwareFileFound")), Times.Once); + c.Data["Reason"] == "AllFilesBlocked")), Times.Once); } #endregion diff --git a/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceCB.cs b/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceCB.cs index 60290ac3..2339febe 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceCB.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceCB.cs @@ -68,7 +68,6 @@ public partial class DelugeService BlocklistType blocklistType = _blocklistProvider.GetBlocklistType(instanceType); ConcurrentBag patterns = _blocklistProvider.GetPatterns(instanceType); ConcurrentBag regexes = _blocklistProvider.GetRegexes(instanceType); - ConcurrentBag malwarePatterns = _blocklistProvider.GetMalwarePatterns(); ProcessFiles(contents.Contents, (name, file) => { @@ -79,13 +78,6 @@ public partial class DelugeService { return; } - - if (malwareBlockerConfig.DeleteKnownMalware && _filenameEvaluator.IsKnownMalware(name, malwarePatterns)) - { - _logger.LogInformation("malware file found | {file} | {title}", file.Path, download.Name); - result.ShouldRemove = true; - result.DeleteReason = DeleteReason.MalwareFileFound; - } if (file.Priority is 0) { diff --git a/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceCB.cs b/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceCB.cs index 0293b669..b7177adc 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceCB.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceCB.cs @@ -73,8 +73,7 @@ public partial class QBitService BlocklistType blocklistType = _blocklistProvider.GetBlocklistType(instanceType); ConcurrentBag patterns = _blocklistProvider.GetPatterns(instanceType); ConcurrentBag regexes = _blocklistProvider.GetRegexes(instanceType); - ConcurrentBag malwarePatterns = _blocklistProvider.GetMalwarePatterns(); - + foreach (TorrentContent file in files) { if (!file.Index.HasValue) @@ -84,14 +83,6 @@ public partial class QBitService } totalFiles++; - - if (malwareBlockerConfig.DeleteKnownMalware && _filenameEvaluator.IsKnownMalware(file.Name, malwarePatterns)) - { - _logger.LogInformation("malware file found | {file} | {title}", file.Name, download.Name); - result.ShouldRemove = true; - result.DeleteReason = DeleteReason.MalwareFileFound; - return result; - } if (file.Priority is TorrentContentPriority.Skip) { diff --git a/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceCB.cs b/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceCB.cs index b98b2e58..9d465f2f 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceCB.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceCB.cs @@ -71,7 +71,6 @@ public partial class RTorrentService BlocklistType blocklistType = _blocklistProvider.GetBlocklistType(instanceType); ConcurrentBag patterns = _blocklistProvider.GetPatterns(instanceType); ConcurrentBag regexes = _blocklistProvider.GetRegexes(instanceType); - ConcurrentBag malwarePatterns = _blocklistProvider.GetMalwarePatterns(); List<(int Index, int Priority)> priorityUpdates = []; @@ -85,13 +84,6 @@ public partial class RTorrentService continue; } - if (malwareBlockerConfig.DeleteKnownMalware && _filenameEvaluator.IsKnownMalware(fileName, malwarePatterns)) - { - _logger.LogInformation("malware file found | {file} | {title}", file.Path, download.Name); - result.ShouldRemove = true; - result.DeleteReason = DeleteReason.MalwareFileFound; - } - if (file.Priority == 0) { _logger.LogTrace("File is already skipped | {file}", file.Path); diff --git a/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceCB.cs b/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceCB.cs index 5f24e983..0243dbf8 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceCB.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceCB.cs @@ -56,8 +56,7 @@ public partial class TransmissionService BlocklistType blocklistType = _blocklistProvider.GetBlocklistType(instanceType); ConcurrentBag patterns = _blocklistProvider.GetPatterns(instanceType); ConcurrentBag regexes = _blocklistProvider.GetRegexes(instanceType); - ConcurrentBag malwarePatterns = _blocklistProvider.GetMalwarePatterns(); - + for (int i = 0; i < download.Files.Length; i++) { if (download.FileStats?[i].Wanted == null) @@ -67,15 +66,7 @@ public partial class TransmissionService } totalFiles++; - - if (malwareBlockerConfig.DeleteKnownMalware && _filenameEvaluator.IsKnownMalware(download.Files[i].Name, malwarePatterns)) - { - _logger.LogInformation("malware file found | {file} | {title}", download.Files[i].Name, download.Name); - result.ShouldRemove = true; - result.DeleteReason = DeleteReason.MalwareFileFound; - return result; - } - + if (!download.FileStats[i].Wanted.Value) { _logger.LogTrace("File is already skipped | {file}", download.Files[i].Name); diff --git a/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceCB.cs b/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceCB.cs index 64f16d9b..996009cc 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceCB.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceCB.cs @@ -61,18 +61,9 @@ public partial class UTorrentService BlocklistType blocklistType = _blocklistProvider.GetBlocklistType(instanceType); ConcurrentBag patterns = _blocklistProvider.GetPatterns(instanceType); ConcurrentBag regexes = _blocklistProvider.GetRegexes(instanceType); - ConcurrentBag malwarePatterns = _blocklistProvider.GetMalwarePatterns(); for (int i = 0; i < files.Count; i++) { - if (malwareBlockerConfig.DeleteKnownMalware && _filenameEvaluator.IsKnownMalware(files[i].Name, malwarePatterns)) - { - _logger.LogInformation("malware file found | {file} | {title}", files[i].Name, download.Name); - result.ShouldRemove = true; - result.DeleteReason = DeleteReason.MalwareFileFound; - return result; - } - var file = files[i]; if (file.Priority == 0) // Already skipped diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Jobs/MalwareBlocker.cs b/code/backend/Cleanuparr.Infrastructure/Features/Jobs/MalwareBlocker.cs index 42b7568b..75a02a28 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Jobs/MalwareBlocker.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Jobs/MalwareBlocker.cs @@ -64,27 +64,27 @@ public sealed class MalwareBlocker : GenericHandler var readarrConfig = ContextProvider.Get(nameof(InstanceType.Readarr)); var whisparrConfig = ContextProvider.Get(nameof(InstanceType.Whisparr)); - if (config.Sonarr.Enabled || config.DeleteKnownMalware) + if (config.Sonarr.Enabled) { await ProcessArrConfigAsync(sonarrConfig); } - - if (config.Radarr.Enabled || config.DeleteKnownMalware) + + if (config.Radarr.Enabled) { await ProcessArrConfigAsync(radarrConfig); } - - if (config.Lidarr.Enabled || config.DeleteKnownMalware) + + if (config.Lidarr.Enabled) { await ProcessArrConfigAsync(lidarrConfig); } - - if (config.Readarr.Enabled || config.DeleteKnownMalware) + + if (config.Readarr.Enabled) { await ProcessArrConfigAsync(readarrConfig); } - - if (config.Whisparr.Enabled || config.DeleteKnownMalware) + + if (config.Whisparr.Enabled) { await ProcessArrConfigAsync(whisparrConfig); } diff --git a/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/BlocklistProvider.cs b/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/BlocklistProvider.cs index 2095d4f7..c22c31f4 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/BlocklistProvider.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/BlocklistProvider.cs @@ -23,8 +23,6 @@ public sealed class BlocklistProvider : IBlocklistProvider private readonly Dictionary _lastLoadTimes = new(); private const int DefaultLoadIntervalHours = 4; private const int FastLoadIntervalMinutes = 5; - private const string MalwareListUrl = "https://cleanuparr.pages.dev/static/known_malware_file_name_patterns"; - private const string MalwareListKey = "MALWARE_PATTERNS"; public BlocklistProvider( ILogger logger, @@ -72,10 +70,7 @@ public sealed class BlocklistProvider : IBlocklistProvider changedCount++; } } - - // Always check and update malware patterns - await LoadMalwarePatternsAsync(fileReader); - + if (changedCount > 0) { _logger.LogInformation("Successfully loaded {count} blocklists", changedCount); @@ -109,17 +104,10 @@ public sealed class BlocklistProvider : IBlocklistProvider public ConcurrentBag GetRegexes(InstanceType instanceType) { _cache.TryGetValue(CacheKeys.BlocklistRegexes(instanceType), out ConcurrentBag? regexes); - + return regexes ?? []; } - - public ConcurrentBag GetMalwarePatterns() - { - _cache.TryGetValue(CacheKeys.KnownMalwarePatterns(), out ConcurrentBag? patterns); - - return patterns ?? []; - } - + private async Task EnsureInstanceLoadedAsync(BlocklistSettings settings, InstanceType instanceType, FileReader fileReader) { if (!settings.Enabled || string.IsNullOrEmpty(settings.BlocklistPath)) @@ -165,47 +153,9 @@ public sealed class BlocklistProvider : IBlocklistProvider { return true; } - + return DateTime.UtcNow - lastLoad >= interval; } - - private async Task LoadMalwarePatternsAsync(FileReader fileReader) - { - var malwareInterval = TimeSpan.FromMinutes(FastLoadIntervalMinutes); - - if (!ShouldReloadBlocklist(MalwareListKey, malwareInterval)) - { - return; - } - - try - { - _logger.LogDebug("Loading malware patterns"); - - string[] filePatterns = await fileReader.ReadContentAsync(MalwareListUrl); - - long startTime = Stopwatch.GetTimestamp(); - ParallelOptions options = new() { MaxDegreeOfParallelism = 5 }; - ConcurrentBag patterns = []; - - Parallel.ForEach(filePatterns, options, pattern => - { - patterns.Add(pattern); - }); - - TimeSpan elapsed = Stopwatch.GetElapsedTime(startTime); - - _cache.Set(CacheKeys.KnownMalwarePatterns(), patterns); - _lastLoadTimes[MalwareListKey] = DateTime.UtcNow; - - _logger.LogDebug("loaded {count} known malware patterns", patterns.Count); - _logger.LogDebug("malware patterns loaded in {elapsed} ms", elapsed.TotalMilliseconds); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to load malware patterns from {url}", MalwareListUrl); - } - } private async Task LoadPatternsAndRegexesAsync(BlocklistSettings blocklistSettings, InstanceType instanceType, FileReader fileReader) { diff --git a/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/FilenameEvaluator.cs b/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/FilenameEvaluator.cs index 3b531a4f..bbe36e4c 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/FilenameEvaluator.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/FilenameEvaluator.cs @@ -20,16 +20,6 @@ public class FilenameEvaluator : IFilenameEvaluator return IsValidAgainstPatterns(filename, type, patterns) && IsValidAgainstRegexes(filename, type, regexes); } - public bool IsKnownMalware(string filename, ConcurrentBag malwarePatterns) - { - if (malwarePatterns.Count is 0) - { - return false; - } - - return malwarePatterns.Any(pattern => filename.Contains(pattern, StringComparison.InvariantCultureIgnoreCase)); - } - private static bool IsValidAgainstPatterns(string filename, BlocklistType type, ConcurrentBag patterns) { if (patterns.Count is 0) diff --git a/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/IBlocklistProvider.cs b/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/IBlocklistProvider.cs index 5c21fea0..add9dba5 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/IBlocklistProvider.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/IBlocklistProvider.cs @@ -13,6 +13,4 @@ public interface IBlocklistProvider ConcurrentBag GetPatterns(InstanceType instanceType); ConcurrentBag GetRegexes(InstanceType instanceType); - - ConcurrentBag GetMalwarePatterns(); } diff --git a/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/IFilenameEvaluator.cs b/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/IFilenameEvaluator.cs index a1be808a..807c7606 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/IFilenameEvaluator.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/MalwareBlocker/IFilenameEvaluator.cs @@ -7,6 +7,4 @@ namespace Cleanuparr.Infrastructure.Features.MalwareBlocker; public interface IFilenameEvaluator { bool IsValid(string filename, BlocklistType type, ConcurrentBag patterns, ConcurrentBag regexes); - - bool IsKnownMalware(string filename, ConcurrentBag malwarePatterns); } \ No newline at end of file diff --git a/code/backend/Cleanuparr.Infrastructure/Helpers/CacheKeys.cs b/code/backend/Cleanuparr.Infrastructure/Helpers/CacheKeys.cs index e796c89c..bf6cd01b 100644 --- a/code/backend/Cleanuparr.Infrastructure/Helpers/CacheKeys.cs +++ b/code/backend/Cleanuparr.Infrastructure/Helpers/CacheKeys.cs @@ -8,8 +8,6 @@ public static class CacheKeys public static string BlocklistPatterns(InstanceType instanceType) => $"{instanceType.ToString()}_patterns"; public static string BlocklistRegexes(InstanceType instanceType) => $"{instanceType.ToString()}_regexes"; - public static string KnownMalwarePatterns() => "KNOWN_MALWARE_PATTERNS"; - public static string IgnoredDownloads(string name) => $"{name}_ignored"; public static string DownloadMarkedForRemoval(string hash, Uri url) => $"remove_{hash.ToLowerInvariant()}_{url}"; diff --git a/code/backend/Cleanuparr.Persistence/Migrations/Data/20260306144520_RemoveKnownMalwareOption.Designer.cs b/code/backend/Cleanuparr.Persistence/Migrations/Data/20260306144520_RemoveKnownMalwareOption.Designer.cs new file mode 100644 index 00000000..8a6cf187 --- /dev/null +++ b/code/backend/Cleanuparr.Persistence/Migrations/Data/20260306144520_RemoveKnownMalwareOption.Designer.cs @@ -0,0 +1,1300 @@ +// +using System; +using System.Collections.Generic; +using Cleanuparr.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Cleanuparr.Persistence.Migrations.Data +{ + [DbContext(typeof(DataContext))] + [Migration("20260306144520_RemoveKnownMalwareOption")] + partial class RemoveKnownMalwareOption + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.1"); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Arr.ArrConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("FailedImportMaxStrikes") + .HasColumnType("INTEGER") + .HasColumnName("failed_import_max_strikes"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_arr_configs"); + + b.ToTable("arr_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Arr.ArrInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("api_key"); + + b.Property("ArrConfigId") + .HasColumnType("TEXT") + .HasColumnName("arr_config_id"); + + b.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("enabled"); + + b.Property("ExternalUrl") + .HasColumnType("TEXT") + .HasColumnName("external_url"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("url"); + + b.Property("Version") + .HasColumnType("REAL") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_arr_instances"); + + b.HasIndex("ArrConfigId") + .HasDatabaseName("ix_arr_instances_arr_config_id"); + + b.ToTable("arr_instances", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.BlacklistSync.BlacklistSyncConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("BlacklistPath") + .HasColumnType("TEXT") + .HasColumnName("blacklist_path"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("cron_expression"); + + b.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("enabled"); + + b.HasKey("Id") + .HasName("pk_blacklist_sync_configs"); + + b.ToTable("blacklist_sync_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.DownloadCleaner.DownloadCleanerConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("cron_expression"); + + b.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("enabled"); + + b.PrimitiveCollection("IgnoredDownloads") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("ignored_downloads"); + + b.PrimitiveCollection("UnlinkedCategories") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("unlinked_categories"); + + b.Property("UnlinkedEnabled") + .HasColumnType("INTEGER") + .HasColumnName("unlinked_enabled"); + + b.PrimitiveCollection("UnlinkedIgnoredRootDirs") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("unlinked_ignored_root_dirs"); + + b.Property("UnlinkedTargetCategory") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("unlinked_target_category"); + + b.Property("UnlinkedUseTag") + .HasColumnType("INTEGER") + .HasColumnName("unlinked_use_tag"); + + b.Property("UseAdvancedScheduling") + .HasColumnType("INTEGER") + .HasColumnName("use_advanced_scheduling"); + + b.HasKey("Id") + .HasName("pk_download_cleaner_configs"); + + b.ToTable("download_cleaner_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.DownloadCleaner.SeedingRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("DeleteSourceFiles") + .HasColumnType("INTEGER") + .HasColumnName("delete_source_files"); + + b.Property("DownloadCleanerConfigId") + .HasColumnType("TEXT") + .HasColumnName("download_cleaner_config_id"); + + b.Property("MaxRatio") + .HasColumnType("REAL") + .HasColumnName("max_ratio"); + + b.Property("MaxSeedTime") + .HasColumnType("REAL") + .HasColumnName("max_seed_time"); + + b.Property("MinSeedTime") + .HasColumnType("REAL") + .HasColumnName("min_seed_time"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("PrivacyType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("privacy_type"); + + b.HasKey("Id") + .HasName("pk_seeding_rules"); + + b.HasIndex("DownloadCleanerConfigId") + .HasDatabaseName("ix_seeding_rules_download_cleaner_config_id"); + + b.ToTable("seeding_rules", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.DownloadClientConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("enabled"); + + b.Property("ExternalUrl") + .HasColumnType("TEXT") + .HasColumnName("external_url"); + + b.Property("Host") + .HasColumnType("TEXT") + .HasColumnName("host"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("Password") + .HasColumnType("TEXT") + .HasColumnName("password"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.Property("TypeName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("type_name"); + + b.Property("UrlBase") + .HasColumnType("TEXT") + .HasColumnName("url_base"); + + b.Property("Username") + .HasColumnType("TEXT") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_download_clients"); + + b.ToTable("download_clients", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.General.GeneralConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("DisplaySupportBanner") + .HasColumnType("INTEGER") + .HasColumnName("display_support_banner"); + + b.Property("DryRun") + .HasColumnType("INTEGER") + .HasColumnName("dry_run"); + + b.Property("EncryptionKey") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("encryption_key"); + + b.Property("HttpCertificateValidation") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("http_certificate_validation"); + + b.Property("HttpMaxRetries") + .HasColumnType("INTEGER") + .HasColumnName("http_max_retries"); + + b.Property("HttpTimeout") + .HasColumnType("INTEGER") + .HasColumnName("http_timeout"); + + b.PrimitiveCollection("IgnoredDownloads") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("ignored_downloads"); + + b.Property("SearchDelay") + .HasColumnType("INTEGER") + .HasColumnName("search_delay"); + + b.Property("SearchEnabled") + .HasColumnType("INTEGER") + .HasColumnName("search_enabled"); + + b.Property("StatusCheckEnabled") + .HasColumnType("INTEGER") + .HasColumnName("status_check_enabled"); + + b.Property("StrikeInactivityWindowHours") + .HasColumnType("INTEGER") + .HasColumnName("strike_inactivity_window_hours"); + + b.ComplexProperty(typeof(Dictionary), "Auth", "Cleanuparr.Persistence.Models.Configuration.General.GeneralConfig.Auth#AuthConfig", b1 => + { + b1.IsRequired(); + + b1.Property("DisableAuthForLocalAddresses") + .HasColumnType("INTEGER") + .HasColumnName("auth_disable_auth_for_local_addresses"); + + b1.Property("TrustForwardedHeaders") + .HasColumnType("INTEGER") + .HasColumnName("auth_trust_forwarded_headers"); + + b1.Property("TrustedNetworks") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("auth_trusted_networks"); + }); + + b.ComplexProperty(typeof(Dictionary), "Log", "Cleanuparr.Persistence.Models.Configuration.General.GeneralConfig.Log#LoggingConfig", b1 => + { + b1.IsRequired(); + + b1.Property("ArchiveEnabled") + .HasColumnType("INTEGER") + .HasColumnName("log_archive_enabled"); + + b1.Property("ArchiveRetainedCount") + .HasColumnType("INTEGER") + .HasColumnName("log_archive_retained_count"); + + b1.Property("ArchiveTimeLimitHours") + .HasColumnType("INTEGER") + .HasColumnName("log_archive_time_limit_hours"); + + b1.Property("Level") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("log_level"); + + b1.Property("RetainedFileCount") + .HasColumnType("INTEGER") + .HasColumnName("log_retained_file_count"); + + b1.Property("RollingSizeMB") + .HasColumnType("INTEGER") + .HasColumnName("log_rolling_size_mb"); + + b1.Property("TimeLimitHours") + .HasColumnType("INTEGER") + .HasColumnName("log_time_limit_hours"); + }); + + b.HasKey("Id") + .HasName("pk_general_configs"); + + b.ToTable("general_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.MalwareBlocker.ContentBlockerConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("cron_expression"); + + b.Property("DeletePrivate") + .HasColumnType("INTEGER") + .HasColumnName("delete_private"); + + b.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("enabled"); + + b.Property("IgnorePrivate") + .HasColumnType("INTEGER") + .HasColumnName("ignore_private"); + + b.PrimitiveCollection("IgnoredDownloads") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("ignored_downloads"); + + b.Property("UseAdvancedScheduling") + .HasColumnType("INTEGER") + .HasColumnName("use_advanced_scheduling"); + + b.ComplexProperty(typeof(Dictionary), "Lidarr", "Cleanuparr.Persistence.Models.Configuration.MalwareBlocker.ContentBlockerConfig.Lidarr#BlocklistSettings", b1 => + { + b1.IsRequired(); + + b1.Property("BlocklistPath") + .HasColumnType("TEXT") + .HasColumnName("lidarr_blocklist_path"); + + b1.Property("BlocklistType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("lidarr_blocklist_type"); + + b1.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("lidarr_enabled"); + }); + + b.ComplexProperty(typeof(Dictionary), "Radarr", "Cleanuparr.Persistence.Models.Configuration.MalwareBlocker.ContentBlockerConfig.Radarr#BlocklistSettings", b1 => + { + b1.IsRequired(); + + b1.Property("BlocklistPath") + .HasColumnType("TEXT") + .HasColumnName("radarr_blocklist_path"); + + b1.Property("BlocklistType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("radarr_blocklist_type"); + + b1.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("radarr_enabled"); + }); + + b.ComplexProperty(typeof(Dictionary), "Readarr", "Cleanuparr.Persistence.Models.Configuration.MalwareBlocker.ContentBlockerConfig.Readarr#BlocklistSettings", b1 => + { + b1.IsRequired(); + + b1.Property("BlocklistPath") + .HasColumnType("TEXT") + .HasColumnName("readarr_blocklist_path"); + + b1.Property("BlocklistType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("readarr_blocklist_type"); + + b1.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("readarr_enabled"); + }); + + b.ComplexProperty(typeof(Dictionary), "Sonarr", "Cleanuparr.Persistence.Models.Configuration.MalwareBlocker.ContentBlockerConfig.Sonarr#BlocklistSettings", b1 => + { + b1.IsRequired(); + + b1.Property("BlocklistPath") + .HasColumnType("TEXT") + .HasColumnName("sonarr_blocklist_path"); + + b1.Property("BlocklistType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sonarr_blocklist_type"); + + b1.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("sonarr_enabled"); + }); + + b.ComplexProperty(typeof(Dictionary), "Whisparr", "Cleanuparr.Persistence.Models.Configuration.MalwareBlocker.ContentBlockerConfig.Whisparr#BlocklistSettings", b1 => + { + b1.IsRequired(); + + b1.Property("BlocklistPath") + .HasColumnType("TEXT") + .HasColumnName("whisparr_blocklist_path"); + + b1.Property("BlocklistType") + .HasColumnType("INTEGER") + .HasColumnName("whisparr_blocklist_type"); + + b1.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("whisparr_enabled"); + }); + + b.HasKey("Id") + .HasName("pk_content_blocker_configs"); + + b.ToTable("content_blocker_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.AppriseConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT") + .HasColumnName("key"); + + b.Property("Mode") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("mode"); + + b.Property("NotificationConfigId") + .HasColumnType("TEXT") + .HasColumnName("notification_config_id"); + + b.Property("ServiceUrls") + .HasMaxLength(4000) + .HasColumnType("TEXT") + .HasColumnName("service_urls"); + + b.Property("Tags") + .HasMaxLength(255) + .HasColumnType("TEXT") + .HasColumnName("tags"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("url"); + + b.HasKey("Id") + .HasName("pk_apprise_configs"); + + b.HasIndex("NotificationConfigId") + .IsUnique() + .HasDatabaseName("ix_apprise_configs_notification_config_id"); + + b.ToTable("apprise_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.DiscordConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("AvatarUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("avatar_url"); + + b.Property("NotificationConfigId") + .HasColumnType("TEXT") + .HasColumnName("notification_config_id"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("TEXT") + .HasColumnName("username"); + + b.Property("WebhookUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("webhook_url"); + + b.HasKey("Id") + .HasName("pk_discord_configs"); + + b.HasIndex("NotificationConfigId") + .IsUnique() + .HasDatabaseName("ix_discord_configs_notification_config_id"); + + b.ToTable("discord_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.GotifyConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ApplicationToken") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT") + .HasColumnName("application_token"); + + b.Property("NotificationConfigId") + .HasColumnType("TEXT") + .HasColumnName("notification_config_id"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("server_url"); + + b.HasKey("Id") + .HasName("pk_gotify_configs"); + + b.HasIndex("NotificationConfigId") + .IsUnique() + .HasDatabaseName("ix_gotify_configs_notification_config_id"); + + b.ToTable("gotify_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.NotifiarrConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT") + .HasColumnName("api_key"); + + b.Property("ChannelId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT") + .HasColumnName("channel_id"); + + b.Property("NotificationConfigId") + .HasColumnType("TEXT") + .HasColumnName("notification_config_id"); + + b.HasKey("Id") + .HasName("pk_notifiarr_configs"); + + b.HasIndex("NotificationConfigId") + .IsUnique() + .HasDatabaseName("ix_notifiarr_configs_notification_config_id"); + + b.ToTable("notifiarr_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.NotificationConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER") + .HasColumnName("is_enabled"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("OnCategoryChanged") + .HasColumnType("INTEGER") + .HasColumnName("on_category_changed"); + + b.Property("OnDownloadCleaned") + .HasColumnType("INTEGER") + .HasColumnName("on_download_cleaned"); + + b.Property("OnFailedImportStrike") + .HasColumnType("INTEGER") + .HasColumnName("on_failed_import_strike"); + + b.Property("OnQueueItemDeleted") + .HasColumnType("INTEGER") + .HasColumnName("on_queue_item_deleted"); + + b.Property("OnSlowStrike") + .HasColumnType("INTEGER") + .HasColumnName("on_slow_strike"); + + b.Property("OnStalledStrike") + .HasColumnType("INTEGER") + .HasColumnName("on_stalled_strike"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_notification_configs"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("ix_notification_configs_name"); + + b.ToTable("notification_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.NtfyConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("AccessToken") + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("access_token"); + + b.Property("AuthenticationType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("authentication_type"); + + b.Property("NotificationConfigId") + .HasColumnType("TEXT") + .HasColumnName("notification_config_id"); + + b.Property("Password") + .HasMaxLength(255) + .HasColumnType("TEXT") + .HasColumnName("password"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("priority"); + + b.Property("ServerUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("server_url"); + + b.PrimitiveCollection("Tags") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tags"); + + b.PrimitiveCollection("Topics") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("topics"); + + b.Property("Username") + .HasMaxLength(255) + .HasColumnType("TEXT") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_ntfy_configs"); + + b.HasIndex("NotificationConfigId") + .IsUnique() + .HasDatabaseName("ix_ntfy_configs_notification_config_id"); + + b.ToTable("ntfy_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.PushoverConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ApiToken") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT") + .HasColumnName("api_token"); + + b.Property("Devices") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("devices"); + + b.Property("Expire") + .HasColumnType("INTEGER") + .HasColumnName("expire"); + + b.Property("NotificationConfigId") + .HasColumnType("TEXT") + .HasColumnName("notification_config_id"); + + b.Property("Priority") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("priority"); + + b.Property("Retry") + .HasColumnType("INTEGER") + .HasColumnName("retry"); + + b.Property("Sound") + .HasMaxLength(50) + .HasColumnType("TEXT") + .HasColumnName("sound"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tags"); + + b.Property("UserKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT") + .HasColumnName("user_key"); + + b.HasKey("Id") + .HasName("pk_pushover_configs"); + + b.HasIndex("NotificationConfigId") + .IsUnique() + .HasDatabaseName("ix_pushover_configs_notification_config_id"); + + b.ToTable("pushover_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.TelegramConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("BotToken") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT") + .HasColumnName("bot_token"); + + b.Property("ChatId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("chat_id"); + + b.Property("NotificationConfigId") + .HasColumnType("TEXT") + .HasColumnName("notification_config_id"); + + b.Property("SendSilently") + .HasColumnType("INTEGER") + .HasColumnName("send_silently"); + + b.Property("TopicId") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("topic_id"); + + b.HasKey("Id") + .HasName("pk_telegram_configs"); + + b.HasIndex("NotificationConfigId") + .IsUnique() + .HasDatabaseName("ix_telegram_configs_notification_config_id"); + + b.ToTable("telegram_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.QueueCleaner.QueueCleanerConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("cron_expression"); + + b.Property("DownloadingMetadataMaxStrikes") + .HasColumnType("INTEGER") + .HasColumnName("downloading_metadata_max_strikes"); + + b.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("enabled"); + + b.PrimitiveCollection("IgnoredDownloads") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("ignored_downloads"); + + b.Property("UseAdvancedScheduling") + .HasColumnType("INTEGER") + .HasColumnName("use_advanced_scheduling"); + + b.ComplexProperty(typeof(Dictionary), "FailedImport", "Cleanuparr.Persistence.Models.Configuration.QueueCleaner.QueueCleanerConfig.FailedImport#FailedImportConfig", b1 => + { + b1.IsRequired(); + + b1.Property("DeletePrivate") + .HasColumnType("INTEGER") + .HasColumnName("failed_import_delete_private"); + + b1.Property("IgnorePrivate") + .HasColumnType("INTEGER") + .HasColumnName("failed_import_ignore_private"); + + b1.Property("MaxStrikes") + .HasColumnType("INTEGER") + .HasColumnName("failed_import_max_strikes"); + + b1.Property("PatternMode") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("failed_import_pattern_mode"); + + b1.PrimitiveCollection("Patterns") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("failed_import_patterns"); + + b1.Property("SkipIfNotFoundInClient") + .HasColumnType("INTEGER") + .HasColumnName("failed_import_skip_if_not_found_in_client"); + }); + + b.HasKey("Id") + .HasName("pk_queue_cleaner_configs"); + + b.ToTable("queue_cleaner_configs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.QueueCleaner.SlowRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("DeletePrivateTorrentsFromClient") + .HasColumnType("INTEGER") + .HasColumnName("delete_private_torrents_from_client"); + + b.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("enabled"); + + b.Property("IgnoreAboveSize") + .HasColumnType("TEXT") + .HasColumnName("ignore_above_size"); + + b.Property("MaxCompletionPercentage") + .HasColumnType("INTEGER") + .HasColumnName("max_completion_percentage"); + + b.Property("MaxStrikes") + .HasColumnType("INTEGER") + .HasColumnName("max_strikes"); + + b.Property("MaxTimeHours") + .HasColumnType("REAL") + .HasColumnName("max_time_hours"); + + b.Property("MinCompletionPercentage") + .HasColumnType("INTEGER") + .HasColumnName("min_completion_percentage"); + + b.Property("MinSpeed") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("min_speed"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("PrivacyType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("privacy_type"); + + b.Property("QueueCleanerConfigId") + .HasColumnType("TEXT") + .HasColumnName("queue_cleaner_config_id"); + + b.Property("ResetStrikesOnProgress") + .HasColumnType("INTEGER") + .HasColumnName("reset_strikes_on_progress"); + + b.HasKey("Id") + .HasName("pk_slow_rules"); + + b.HasIndex("QueueCleanerConfigId") + .HasDatabaseName("ix_slow_rules_queue_cleaner_config_id"); + + b.ToTable("slow_rules", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.QueueCleaner.StallRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("DeletePrivateTorrentsFromClient") + .HasColumnType("INTEGER") + .HasColumnName("delete_private_torrents_from_client"); + + b.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("enabled"); + + b.Property("MaxCompletionPercentage") + .HasColumnType("INTEGER") + .HasColumnName("max_completion_percentage"); + + b.Property("MaxStrikes") + .HasColumnType("INTEGER") + .HasColumnName("max_strikes"); + + b.Property("MinCompletionPercentage") + .HasColumnType("INTEGER") + .HasColumnName("min_completion_percentage"); + + b.Property("MinimumProgress") + .HasColumnType("TEXT") + .HasColumnName("minimum_progress"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("PrivacyType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("privacy_type"); + + b.Property("QueueCleanerConfigId") + .HasColumnType("TEXT") + .HasColumnName("queue_cleaner_config_id"); + + b.Property("ResetStrikesOnProgress") + .HasColumnType("INTEGER") + .HasColumnName("reset_strikes_on_progress"); + + b.HasKey("Id") + .HasName("pk_stall_rules"); + + b.HasIndex("QueueCleanerConfigId") + .HasDatabaseName("ix_stall_rules_queue_cleaner_config_id"); + + b.ToTable("stall_rules", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.BlacklistSyncHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("DownloadClientId") + .HasColumnType("TEXT") + .HasColumnName("download_client_id"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hash"); + + b.HasKey("Id") + .HasName("pk_blacklist_sync_history"); + + b.HasIndex("DownloadClientId") + .HasDatabaseName("ix_blacklist_sync_history_download_client_id"); + + b.HasIndex("Hash") + .HasDatabaseName("ix_blacklist_sync_history_hash"); + + b.HasIndex("Hash", "DownloadClientId") + .IsUnique() + .HasDatabaseName("ix_blacklist_sync_history_hash_download_client_id"); + + b.ToTable("blacklist_sync_history", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Arr.ArrInstance", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.Arr.ArrConfig", "ArrConfig") + .WithMany("Instances") + .HasForeignKey("ArrConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_arr_instances_arr_configs_arr_config_id"); + + b.Navigation("ArrConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.DownloadCleaner.SeedingRule", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.DownloadCleaner.DownloadCleanerConfig", "DownloadCleanerConfig") + .WithMany("Categories") + .HasForeignKey("DownloadCleanerConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_seeding_rules_download_cleaner_configs_download_cleaner_config_id"); + + b.Navigation("DownloadCleanerConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.AppriseConfig", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.Notification.NotificationConfig", "NotificationConfig") + .WithOne("AppriseConfiguration") + .HasForeignKey("Cleanuparr.Persistence.Models.Configuration.Notification.AppriseConfig", "NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_apprise_configs_notification_configs_notification_config_id"); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.DiscordConfig", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.Notification.NotificationConfig", "NotificationConfig") + .WithOne("DiscordConfiguration") + .HasForeignKey("Cleanuparr.Persistence.Models.Configuration.Notification.DiscordConfig", "NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_discord_configs_notification_configs_notification_config_id"); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.GotifyConfig", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.Notification.NotificationConfig", "NotificationConfig") + .WithOne("GotifyConfiguration") + .HasForeignKey("Cleanuparr.Persistence.Models.Configuration.Notification.GotifyConfig", "NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_gotify_configs_notification_configs_notification_config_id"); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.NotifiarrConfig", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.Notification.NotificationConfig", "NotificationConfig") + .WithOne("NotifiarrConfiguration") + .HasForeignKey("Cleanuparr.Persistence.Models.Configuration.Notification.NotifiarrConfig", "NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_notifiarr_configs_notification_configs_notification_config_id"); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.NtfyConfig", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.Notification.NotificationConfig", "NotificationConfig") + .WithOne("NtfyConfiguration") + .HasForeignKey("Cleanuparr.Persistence.Models.Configuration.Notification.NtfyConfig", "NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_ntfy_configs_notification_configs_notification_config_id"); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.PushoverConfig", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.Notification.NotificationConfig", "NotificationConfig") + .WithOne("PushoverConfiguration") + .HasForeignKey("Cleanuparr.Persistence.Models.Configuration.Notification.PushoverConfig", "NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_pushover_configs_notification_configs_notification_config_id"); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.TelegramConfig", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.Notification.NotificationConfig", "NotificationConfig") + .WithOne("TelegramConfiguration") + .HasForeignKey("Cleanuparr.Persistence.Models.Configuration.Notification.TelegramConfig", "NotificationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_telegram_configs_notification_configs_notification_config_id"); + + b.Navigation("NotificationConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.QueueCleaner.SlowRule", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.QueueCleaner.QueueCleanerConfig", "QueueCleanerConfig") + .WithMany("SlowRules") + .HasForeignKey("QueueCleanerConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_slow_rules_queue_cleaner_configs_queue_cleaner_config_id"); + + b.Navigation("QueueCleanerConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.QueueCleaner.StallRule", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.QueueCleaner.QueueCleanerConfig", "QueueCleanerConfig") + .WithMany("StallRules") + .HasForeignKey("QueueCleanerConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_stall_rules_queue_cleaner_configs_queue_cleaner_config_id"); + + b.Navigation("QueueCleanerConfig"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.BlacklistSyncHistory", b => + { + b.HasOne("Cleanuparr.Persistence.Models.Configuration.DownloadClientConfig", "DownloadClient") + .WithMany() + .HasForeignKey("DownloadClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_blacklist_sync_history_download_clients_download_client_id"); + + b.Navigation("DownloadClient"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Arr.ArrConfig", b => + { + b.Navigation("Instances"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.DownloadCleaner.DownloadCleanerConfig", b => + { + b.Navigation("Categories"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.Notification.NotificationConfig", b => + { + b.Navigation("AppriseConfiguration"); + + b.Navigation("DiscordConfiguration"); + + b.Navigation("GotifyConfiguration"); + + b.Navigation("NotifiarrConfiguration"); + + b.Navigation("NtfyConfiguration"); + + b.Navigation("PushoverConfiguration"); + + b.Navigation("TelegramConfiguration"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Configuration.QueueCleaner.QueueCleanerConfig", b => + { + b.Navigation("SlowRules"); + + b.Navigation("StallRules"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/backend/Cleanuparr.Persistence/Migrations/Data/20260306144520_RemoveKnownMalwareOption.cs b/code/backend/Cleanuparr.Persistence/Migrations/Data/20260306144520_RemoveKnownMalwareOption.cs new file mode 100644 index 00000000..1656a28e --- /dev/null +++ b/code/backend/Cleanuparr.Persistence/Migrations/Data/20260306144520_RemoveKnownMalwareOption.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Cleanuparr.Persistence.Migrations.Data +{ + /// + public partial class RemoveKnownMalwareOption : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "delete_known_malware", + table: "content_blocker_configs"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "delete_known_malware", + table: "content_blocker_configs", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + } +} diff --git a/code/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cs b/code/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cs index a9fa7afe..43603c45 100644 --- a/code/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cs +++ b/code/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cs @@ -389,10 +389,6 @@ namespace Cleanuparr.Persistence.Migrations.Data .HasColumnType("TEXT") .HasColumnName("cron_expression"); - b.Property("DeleteKnownMalware") - .HasColumnType("INTEGER") - .HasColumnName("delete_known_malware"); - b.Property("DeletePrivate") .HasColumnType("INTEGER") .HasColumnName("delete_private"); diff --git a/code/backend/Cleanuparr.Persistence/Models/Configuration/MalwareBlocker/ContentBlockerConfig.cs b/code/backend/Cleanuparr.Persistence/Models/Configuration/MalwareBlocker/ContentBlockerConfig.cs index bc0eca8d..b8cd50b2 100644 --- a/code/backend/Cleanuparr.Persistence/Models/Configuration/MalwareBlocker/ContentBlockerConfig.cs +++ b/code/backend/Cleanuparr.Persistence/Models/Configuration/MalwareBlocker/ContentBlockerConfig.cs @@ -19,8 +19,6 @@ public sealed record ContentBlockerConfig : IJobConfig public bool IgnorePrivate { get; set; } public bool DeletePrivate { get; set; } - - public bool DeleteKnownMalware { get; set; } public BlocklistSettings Sonarr { get; set; } = new(); diff --git a/code/frontend/src/app/core/services/documentation.service.ts b/code/frontend/src/app/core/services/documentation.service.ts index 05fc630e..a2b26a7b 100644 --- a/code/frontend/src/app/core/services/documentation.service.ts +++ b/code/frontend/src/app/core/services/documentation.service.ts @@ -87,7 +87,6 @@ export class DocumentationService { 'cronExpression': 'cron-expression', 'ignorePrivate': 'ignore-private', 'deletePrivate': 'delete-private', - 'deleteKnownMalware': 'delete-known-malware', 'sonarr.enabled': 'enable-blocklist', 'sonarr.blocklistPath': 'blocklist-path', 'sonarr.blocklistType': 'blocklist-type', diff --git a/code/frontend/src/app/features/settings/malware-blocker/malware-blocker.component.html b/code/frontend/src/app/features/settings/malware-blocker/malware-blocker.component.html index 30e16518..ff7e8732 100644 --- a/code/frontend/src/app/features/settings/malware-blocker/malware-blocker.component.html +++ b/code/frontend/src/app/features/settings/malware-blocker/malware-blocker.component.html @@ -23,9 +23,6 @@ hint="When enabled, the Malware blocker will run according to the schedule" helpKey="malware-blocker:enabled" /> @if (enabled()) { - diff --git a/code/frontend/src/app/features/settings/malware-blocker/malware-blocker.component.ts b/code/frontend/src/app/features/settings/malware-blocker/malware-blocker.component.ts index 11f51aec..7012dc4b 100644 --- a/code/frontend/src/app/features/settings/malware-blocker/malware-blocker.component.ts +++ b/code/frontend/src/app/features/settings/malware-blocker/malware-blocker.component.ts @@ -62,7 +62,6 @@ export class MalwareBlockerComponent implements OnInit, HasPendingChanges { readonly ignoredDownloads = signal([]); readonly ignorePrivate = signal(false); readonly deletePrivate = signal(false); - readonly deleteKnownMalware = signal(false); readonly arrExpanded = signal(false); readonly scheduleIntervalOptions = computed(() => { @@ -164,7 +163,6 @@ export class MalwareBlockerComponent implements OnInit, HasPendingChanges { this.ignoredDownloads.set(config.ignoredDownloads ?? []); this.ignorePrivate.set(config.ignorePrivate); this.deletePrivate.set(config.deletePrivate); - this.deleteKnownMalware.set(config.deleteKnownMalware); const blocklists: Record = {}; for (const name of ARR_NAMES) { @@ -220,7 +218,6 @@ export class MalwareBlockerComponent implements OnInit, HasPendingChanges { ignoredDownloads: this.ignoredDownloads(), ignorePrivate: this.ignorePrivate(), deletePrivate: this.deletePrivate(), - deleteKnownMalware: this.deleteKnownMalware(), sonarr: { enabled: blocklists['sonarr'].enabled, blocklistPath: blocklists['sonarr'].blocklistPath, blocklistType: blocklists['sonarr'].blocklistType as BlocklistType }, radarr: { enabled: blocklists['radarr'].enabled, blocklistPath: blocklists['radarr'].blocklistPath, blocklistType: blocklists['radarr'].blocklistType as BlocklistType }, lidarr: { enabled: blocklists['lidarr'].enabled, blocklistPath: blocklists['lidarr'].blocklistPath, blocklistType: blocklists['lidarr'].blocklistType as BlocklistType }, @@ -256,7 +253,6 @@ export class MalwareBlockerComponent implements OnInit, HasPendingChanges { ignoredDownloads: this.ignoredDownloads(), ignorePrivate: this.ignorePrivate(), deletePrivate: this.deletePrivate(), - deleteKnownMalware: this.deleteKnownMalware(), arrBlocklists: this.arrBlocklists(), }); } diff --git a/code/frontend/src/app/shared/models/malware-blocker-config.model.ts b/code/frontend/src/app/shared/models/malware-blocker-config.model.ts index 1db2dd53..75e5d391 100644 --- a/code/frontend/src/app/shared/models/malware-blocker-config.model.ts +++ b/code/frontend/src/app/shared/models/malware-blocker-config.model.ts @@ -21,7 +21,6 @@ export interface MalwareBlockerConfig { ignoredDownloads: string[]; ignorePrivate: boolean; deletePrivate: boolean; - deleteKnownMalware: boolean; sonarr: BlocklistSettings; radarr: BlocklistSettings; lidarr: BlocklistSettings; diff --git a/docs/docs/2_features.mdx b/docs/docs/2_features.mdx index ea046dca..497763fe 100644 --- a/docs/docs/2_features.mdx +++ b/docs/docs/2_features.mdx @@ -69,7 +69,6 @@ Advanced download management and automation features for your *arr applications > - Remove and block downloads blocked by qBittorrent or by Cleanuparr's **Malware Blocker**. -- Remove and block known malware based on patterns found by the community. diff --git a/docs/docs/configuration/malware-blocker/index.mdx b/docs/docs/configuration/malware-blocker/index.mdx index 34a23e39..c078b205 100644 --- a/docs/docs/configuration/malware-blocker/index.mdx +++ b/docs/docs/configuration/malware-blocker/index.mdx @@ -105,24 +105,6 @@ Setting this to true means private torrents will be permanently deleted, potenti - - -When enabled, downloads that match known malware patterns will be automatically deleted from the download client. - -**Malware Detection Source:** -- List is automatically fetched from: `https://cleanuparr.pages.dev/static/known_malware_file_name_patterns` -- Updates automatically every **5 minutes** -- Contains filename patterns known to be associated with malware - - -This feature permanently deletes downloads that match malware patterns. While the patterns are carefully curated, false positives are possible. Monitor logs carefully when first enabling this feature. - - - -