mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 18:58:59 -04:00
Fix rTorrent seeding rules not respecting tracker settings (#808)
This commit is contained in:
1 parent
8920539c47
commit
5a1bd63c68
5 files changed
+330
-6
No files matched your search
+97
@@ -1,6 +1,7 @@
|
||||
using Cleanuparr.Domain.Entities;
|
||||
using Cleanuparr.Domain.Entities.RTorrent.Response;
|
||||
using Cleanuparr.Infrastructure.Features.DownloadClient.RTorrent;
|
||||
using Cleanuparr.Persistence.Models.Configuration;
|
||||
using Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
@@ -43,6 +44,14 @@ public class RTorrentServiceDCTests : IClassFixture<RTorrentServiceFixture>
|
||||
.GetAllTorrentsAsync()
|
||||
.Returns(downloads);
|
||||
|
||||
_fixture.ClientWrapper
|
||||
.GetTrackersAsync("HASH1")
|
||||
.Returns(new List<string>());
|
||||
|
||||
_fixture.ClientWrapper
|
||||
.GetTrackersAsync("HASH3")
|
||||
.Returns(new List<string>());
|
||||
|
||||
// Act
|
||||
var result = await sut.GetSeedingDownloads();
|
||||
|
||||
@@ -84,6 +93,10 @@ public class RTorrentServiceDCTests : IClassFixture<RTorrentServiceFixture>
|
||||
.GetAllTorrentsAsync()
|
||||
.Returns(downloads);
|
||||
|
||||
_fixture.ClientWrapper
|
||||
.GetTrackersAsync("HASH1")
|
||||
.Returns(new List<string>());
|
||||
|
||||
// Act
|
||||
var result = await sut.GetSeedingDownloads();
|
||||
|
||||
@@ -91,6 +104,44 @@ public class RTorrentServiceDCTests : IClassFixture<RTorrentServiceFixture>
|
||||
result.ShouldHaveSingleItem();
|
||||
result[0].Hash.ShouldBe("HASH1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FetchesTrackerDataForSeedingTorrents()
|
||||
{
|
||||
// Arrange
|
||||
var sut = _fixture.CreateSut();
|
||||
|
||||
var downloads = new List<RTorrentTorrent>
|
||||
{
|
||||
new RTorrentTorrent { Hash = "HASH1", Name = "Torrent 1", State = 1, Complete = 1, IsPrivate = 0, Label = "" }
|
||||
};
|
||||
|
||||
var trackers = new List<string>
|
||||
{
|
||||
"http://tracker.example.com:6969/announce",
|
||||
"http://tracker.other.org:8080/announce"
|
||||
};
|
||||
|
||||
_fixture.ClientWrapper
|
||||
.GetAllTorrentsAsync()
|
||||
.Returns(downloads);
|
||||
|
||||
_fixture.ClientWrapper
|
||||
.GetTrackersAsync("HASH1")
|
||||
.Returns(trackers);
|
||||
|
||||
// Act
|
||||
var result = await sut.GetSeedingDownloads();
|
||||
|
||||
// Assert
|
||||
result.ShouldHaveSingleItem();
|
||||
var wrapper = result[0];
|
||||
wrapper.TrackerDomains.ShouldContain("tracker.example.com");
|
||||
wrapper.TrackerDomains.ShouldContain("tracker.other.org");
|
||||
|
||||
await _fixture.ClientWrapper.Received(1)
|
||||
.GetTrackersAsync("HASH1");
|
||||
}
|
||||
}
|
||||
|
||||
public class FilterDownloadsToBeCleanedAsync_Tests : RTorrentServiceDCTests
|
||||
@@ -297,6 +348,52 @@ public class RTorrentServiceDCTests : IClassFixture<RTorrentServiceFixture>
|
||||
await _fixture.ClientWrapper.Received(1)
|
||||
.DeleteTorrentAsync("LOWERCASE");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RemapsSavePathBeforeDeletingFiles()
|
||||
{
|
||||
// Arrange
|
||||
string targetRoot = Directory.CreateTempSubdirectory().FullName;
|
||||
string targetPath = Path.Combine(targetRoot, "seed-rule-tracker");
|
||||
Directory.CreateDirectory(targetPath);
|
||||
File.WriteAllText(Path.Combine(targetPath, "file.txt"), "content");
|
||||
|
||||
DownloadClientConfig config = new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Test rTorrent Client",
|
||||
TypeName = Domain.Enums.DownloadClientTypeName.rTorrent,
|
||||
Type = Domain.Enums.DownloadClientType.Torrent,
|
||||
Enabled = true,
|
||||
Host = new Uri("http://localhost/RPC2"),
|
||||
Username = "admin",
|
||||
Password = "admin",
|
||||
UrlBase = "",
|
||||
DownloadDirectorySource = "/downloads",
|
||||
DownloadDirectoryTarget = targetRoot,
|
||||
};
|
||||
var sut = _fixture.CreateSut(config);
|
||||
|
||||
var mockTorrent = Substitute.For<ITorrentItemWrapper>();
|
||||
mockTorrent.Hash.Returns("hash");
|
||||
mockTorrent.SavePath.Returns("/downloads/seed-rule-tracker");
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await sut.DeleteDownload(mockTorrent, deleteSourceFiles: true);
|
||||
|
||||
// Assert
|
||||
Directory.Exists(targetPath).ShouldBeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(targetRoot))
|
||||
{
|
||||
Directory.Delete(targetRoot, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class StopDownload_Tests : RTorrentServiceDCTests
|
||||
|
||||
@@ -64,6 +64,23 @@ public class SeedingRuleEvaluatorTests
|
||||
};
|
||||
}
|
||||
|
||||
private static RTorrentSeedingRule CreateRTorrentRule(
|
||||
int priority = 1,
|
||||
List<string>? categories = null,
|
||||
List<string>? trackerPatterns = null,
|
||||
TorrentPrivacyType privacyType = TorrentPrivacyType.Both)
|
||||
{
|
||||
return new RTorrentSeedingRule
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Test Rule",
|
||||
Priority = priority,
|
||||
Categories = categories ?? ["movies"],
|
||||
TrackerPatterns = trackerPatterns ?? [],
|
||||
PrivacyType = privacyType,
|
||||
};
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Empty / null rules
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
@@ -272,6 +289,19 @@ public class SeedingRuleEvaluatorTests
|
||||
_sut.GetMatchingRule(torrent, [rule]).ShouldBe(rule);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMatchingRule_RTorrentRule_TrackerPatternMatches()
|
||||
{
|
||||
var torrent = CreateTorrent(
|
||||
category: "movies",
|
||||
trackerDomains: ["tracker.example.com"]);
|
||||
var rule = CreateRTorrentRule(
|
||||
categories: ["movies"],
|
||||
trackerPatterns: ["example.com"]);
|
||||
|
||||
_sut.GetMatchingRule(torrent, [rule]).ShouldBe(rule);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Priority ordering
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
+15
-6
@@ -13,12 +13,16 @@ public partial class RTorrentService
|
||||
{
|
||||
var downloads = await _client.GetAllTorrentsAsync();
|
||||
|
||||
return downloads
|
||||
var result = new List<ITorrentItemWrapper>();
|
||||
foreach (var torrent in downloads
|
||||
.Where(x => !string.IsNullOrEmpty(x.Hash))
|
||||
// Seeding: complete=1 (finished) and state=1 (started)
|
||||
.Where(x => x is { Complete: 1, State: 1 })
|
||||
.Select(ITorrentItemWrapper (x) => new RTorrentItemWrapper(x, null, _timeProvider))
|
||||
.ToList();
|
||||
.Where(x => x is { Complete: 1, State: 1 }))
|
||||
{
|
||||
var trackers = await _client.GetTrackersAsync(torrent.Hash);
|
||||
result.Add(new RTorrentItemWrapper(torrent, trackers, _timeProvider));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -81,7 +85,12 @@ public partial class RTorrentService
|
||||
|
||||
if (deleteSourceFiles)
|
||||
{
|
||||
if (!TryDeleteFiles(torrent.SavePath, true))
|
||||
string savePath = PathHelper.NormalizeAndRemap(
|
||||
torrent.SavePath,
|
||||
_downloadClientConfig.DownloadDirectorySource,
|
||||
_downloadClientConfig.DownloadDirectoryTarget);
|
||||
|
||||
if (!TryDeleteFiles(savePath, true))
|
||||
{
|
||||
_logger.LogWarning("Failed to delete files | {name}", torrent.Name);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user