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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,19 @@ private class TestProxy : IQBittorrentProxy

public QBittorrentPreferences GetConfig(QBittorrentSettings settings) => new QBittorrentPreferences { DhtEnabled = true };

public List<QBittorrentTorrent> GetTorrents(QBittorrentSettings settings, string category = null) => throw new NotImplementedException();
public Dictionary<string, List<string>> HashesByCategory { get; } = new(StringComparer.OrdinalIgnoreCase);
public bool TorrentLoaded { get; set; }

public List<QBittorrentTorrent> GetTorrents(QBittorrentSettings settings, string category = null)
{
var hashes = category != null && HashesByCategory.TryGetValue(category, out var found) ? found : new List<string>();
return hashes.ConvertAll(hash => new QBittorrentTorrent { Hash = hash });
}

public bool IsTorrentLoaded(string hash, QBittorrentSettings settings)
{
LoadedHashes.Add(hash);
return false;
return TorrentLoaded;
}

public QBittorrentTorrentProperties GetTorrentProperties(string hash, QBittorrentSettings settings) => throw new NotImplementedException();
Expand Down Expand Up @@ -242,11 +249,84 @@ public void should_surface_non_conflict_errors_unchanged()
Assert.Multiple(() =>
{
Assert.That(exception, Is.SameAs(original));
Assert.That(proxy.Labels, Is.Empty);
});
}

[Test]
public void should_continue_with_existing_torrent_already_in_a_chaptarr_category()
{
var proxy = new TestProxy { AddException = new DownloadClientException("qBittorrent rejected the torrent add request (response: Fails.).") };
proxy.HashesByCategory["ebooks"] = new List<string> { "abcdef1234" };
var client = CreateClient(proxy);

var result = client.AddTorrentFile(CreateRemoteBook(), "ABCDEF1234");

Assert.Multiple(() =>
{
Assert.That(result, Is.EqualTo("ABCDEF1234"));
Assert.That(proxy.LoadedHashes, Is.Empty);
Assert.That(proxy.Labels, Is.Empty);
});
}

[Test]
public void should_move_existing_torrent_back_from_post_import_category()
{
var proxy = new TestProxy { AddException = new DownloadClientException("qBittorrent rejected the torrent add request (response: Fails.).") };
proxy.HashesByCategory["ebooks-imported"] = new List<string> { "abcdef1234" };
var client = CreateClient(proxy);

client.AddMagnet(CreateRemoteBook(), "ABCDEF1234");

Assert.That(proxy.Labels, Is.EqualTo(new[] { ("abcdef1234", "ebooks") }));
}

[Test]
public void should_reject_without_adopting_when_existing_torrent_is_outside_chaptarr_categories()
{
var proxy = new TestProxy
{
AddException = new DownloadClientException("qBittorrent rejected the torrent add request (response: Fails.)."),
TorrentLoaded = true
};
var client = CreateClient(proxy);

var exception = Assert.Throws<DownloadClientRejectedReleaseException>(() => client.AddTorrentFile(CreateRemoteBook(), "ABCDEF1234"));

Assert.Multiple(() =>
{
Assert.That(exception.Message, Is.EqualTo("qBittorrent already has this torrent outside Chaptarr's categories"));
Assert.That(proxy.Labels, Is.Empty);
});
}

private static TestQBittorrent CreateClient(TestProxy proxy)
{
return new TestQBittorrent(new TestProxySelector(proxy, new Version(2, 11, 0)), new CacheManager(), LogManager.GetCurrentClassLogger())
{
Definition = new DownloadClientDefinition
{
Name = "qBittorrent",
Settings = new QBittorrentSettings
{
EbookCategory = "ebooks",
AudiobookCategory = "audiobooks",
EbookImportedCategory = "ebooks-imported"
}
}
};
}

private static RemoteBook CreateRemoteBook()
{
return new RemoteBook
{
Books = new List<Book> { new Book { MediaType = BookMediaType.Ebook } },
Release = new TorrentInfo()
};
}

private static DownloadClientException ConflictException()
{
var request = new HttpRequest("http://localhost/api/v2/torrents/add");
Expand Down
65 changes: 65 additions & 0 deletions src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,57 @@ public bool ShouldPreserveItemAfterImport(DownloadClientItem downloadClientItem)
Settings.EbookImportedCategory);
}

private bool TryAdoptExistingTorrent(RemoteBook remoteBook, string hash, string category, DownloadClientException addException)
{
if (hash.IsNullOrWhiteSpace())
{
return false;
}

string existingCategory;
bool existsElsewhere;

try
{
existingCategory = new[] { Settings.AudiobookCategory, Settings.EbookCategory, Settings.AudiobookImportedCategory, Settings.EbookImportedCategory }
.Where(c => c.IsNotNullOrWhiteSpace())
.Distinct(StringComparer.InvariantCultureIgnoreCase)
.FirstOrDefault(c => Proxy.GetTorrents(Settings, c).Any(t => hash.Equals(t.Hash, StringComparison.OrdinalIgnoreCase)));

existsElsewhere = existingCategory == null && Proxy.IsTorrentLoaded(hash.ToLower(), Settings);
}
catch (Exception ex)
{
_logger.Debug(ex, "Unable to check whether torrent {0} already exists in qBittorrent", hash);
return false;
}

if (existsElsewhere)
{
throw new DownloadClientRejectedReleaseException(remoteBook.Release, "qBittorrent already has this torrent outside Chaptarr's categories", addException);
}

if (existingCategory == null)
{
return false;
}

if (category.IsNotNullOrWhiteSpace() && !category.Equals(existingCategory, StringComparison.InvariantCultureIgnoreCase))
{
try
{
Proxy.SetTorrentLabel(hash.ToLower(), category, Settings);
}
catch (DownloadClientException ex)
{
_logger.Warn(ex, "Failed to move existing torrent {0} from category \"{1}\" to \"{2}\" in qBittorrent.", hash, existingCategory, category);
}
}

_logger.Info("qBittorrent already has torrent {0} in Chaptarr category \"{1}\"; continuing with the existing download instead of failing the grab", hash, existingCategory);
return true;
}

protected override string AddFromMagnetLink(RemoteBook remoteBook, string hash, string magnetLink)
{
if (!Proxy.GetConfig(Settings).DhtEnabled && !magnetLink.Contains("&tr="))
Expand All @@ -113,6 +164,13 @@ protected override string AddFromMagnetLink(RemoteBook remoteBook, string hash,
{
throw new DownloadClientRejectedReleaseException(remoteBook.Release, "qBittorrent rejected the magnet link due to a conflict", ex);
}
catch (DownloadClientException ex)
{
if (!TryAdoptExistingTorrent(remoteBook, hash, category, ex))
{
throw;
}
}

if ((!addHasSetShareLimits && setShareLimits) || moveToTop || forceStart)
{
Expand Down Expand Up @@ -178,6 +236,13 @@ protected override string AddFromTorrentFile(RemoteBook remoteBook, string hash,
{
throw new DownloadClientRejectedReleaseException(remoteBook.Release, "qBittorrent rejected the torrent file due to a conflict", ex);
}
catch (DownloadClientException ex)
{
if (!TryAdoptExistingTorrent(remoteBook, hash, category, ex))
{
throw;
}
}

if ((!addHasSetShareLimits && setShareLimits) || moveToTop || forceStart)
{
Expand Down