New: Vuze torrent client support

This commit is contained in:
Igal Tabachnik 2016-05-03 10:58:28 +03:00 committed by Mark McDowall
parent 229986033c
commit a6b1a1fc0d
9 changed files with 863 additions and 423 deletions

View File

@ -1,201 +1,17 @@
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Transmission; using NzbDrone.Core.Download.Clients.Transmission;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
{ {
[TestFixture] [TestFixture]
public class TransmissionFixture : DownloadClientFixtureBase<Transmission> public class TransmissionFixture : TransmissionFixtureBase<Transmission>
{ {
protected TransmissionSettings _settings;
protected TransmissionTorrent _queued;
protected TransmissionTorrent _downloading;
protected TransmissionTorrent _failed;
protected TransmissionTorrent _completed;
protected TransmissionTorrent _magnet;
protected Dictionary<string, object> _transmissionConfigItems;
[SetUp]
public void Setup()
{
_settings = new TransmissionSettings
{
Host = "127.0.0.1",
Port = 2222,
Username = "admin",
Password = "pass"
};
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = _settings;
_queued = new TransmissionTorrent
{
HashString = "HASH",
IsFinished = false,
Status = TransmissionTorrentStatus.Queued,
Name = _title,
TotalSize = 1000,
LeftUntilDone = 1000,
DownloadDir = "somepath"
};
_downloading = new TransmissionTorrent
{
HashString = "HASH",
IsFinished = false,
Status = TransmissionTorrentStatus.Downloading,
Name = _title,
TotalSize = 1000,
LeftUntilDone = 100,
DownloadDir = "somepath"
};
_failed = new TransmissionTorrent
{
HashString = "HASH",
IsFinished = false,
Status = TransmissionTorrentStatus.Stopped,
Name = _title,
TotalSize = 1000,
LeftUntilDone = 100,
ErrorString = "Error",
DownloadDir = "somepath"
};
_completed = new TransmissionTorrent
{
HashString = "HASH",
IsFinished = true,
Status = TransmissionTorrentStatus.Stopped,
Name = _title,
TotalSize = 1000,
LeftUntilDone = 0,
DownloadDir = "somepath"
};
_magnet = new TransmissionTorrent
{
HashString = "HASH",
IsFinished = false,
Status = TransmissionTorrentStatus.Downloading,
Name = _title,
TotalSize = 0,
LeftUntilDone = 100,
DownloadDir = "somepath"
};
Mocker.GetMock<ITorrentFileInfoReader>()
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<byte[]>()))
.Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951");
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
_transmissionConfigItems = new Dictionary<string, object>();
_transmissionConfigItems.Add("download-dir", @"C:/Downloads/Finished/transmission");
_transmissionConfigItems.Add("incomplete-dir", null);
_transmissionConfigItems.Add("incomplete-dir-enabled", false);
Mocker.GetMock<ITransmissionProxy>()
.Setup(v => v.GetConfig(It.IsAny<TransmissionSettings>()))
.Returns(_transmissionConfigItems);
}
protected void GivenTvCategory()
{
_settings.TvCategory = "sonarr";
}
protected void GivenTvDirectory()
{
_settings.TvDirectory = @"C:/Downloads/Finished/sonarr";
}
protected void GivenFailedDownload()
{
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TransmissionSettings>()))
.Throws<InvalidOperationException>();
}
protected void GivenSuccessfulDownload()
{
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TransmissionSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.AddTorrentFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<TransmissionSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
}
protected virtual void GivenTorrents(List<TransmissionTorrent> torrents)
{
if (torrents == null)
{
torrents = new List<TransmissionTorrent>();
}
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.GetTorrents(It.IsAny<TransmissionSettings>()))
.Returns(torrents);
}
protected void PrepareClientToReturnQueuedItem()
{
GivenTorrents(new List<TransmissionTorrent>
{
_queued
});
}
protected void PrepareClientToReturnDownloadingItem()
{
GivenTorrents(new List<TransmissionTorrent>
{
_downloading
});
}
protected void PrepareClientToReturnFailedItem()
{
GivenTorrents(new List<TransmissionTorrent>
{
_failed
});
}
protected void PrepareClientToReturnCompletedItem()
{
GivenTorrents(new List<TransmissionTorrent>
{
_completed
});
}
protected void PrepareClientToReturnMagnetItem()
{
GivenTorrents(new List<TransmissionTorrent>
{
_magnet
});
}
[Test] [Test]
public void queued_item_should_have_required_properties() public void queued_item_should_have_required_properties()
{ {
@ -260,7 +76,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
Mocker.GetMock<ITransmissionProxy>() Mocker.GetMock<ITransmissionProxy>()
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/sonarr", It.IsAny<TransmissionSettings>()), Times.Once()); .Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/sonarr", It.IsAny<TransmissionSettings>()), Times.Once());
} }
[Test] [Test]
@ -276,7 +92,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
Mocker.GetMock<ITransmissionProxy>() Mocker.GetMock<ITransmissionProxy>()
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny<TransmissionSettings>()), Times.Once()); .Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny<TransmissionSettings>()), Times.Once());
} }
[Test] [Test]
@ -294,7 +110,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
Mocker.GetMock<ITransmissionProxy>() Mocker.GetMock<ITransmissionProxy>()
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny<TransmissionSettings>()), Times.Once()); .Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny<TransmissionSettings>()), Times.Once());
} }
[Test] [Test]
@ -309,7 +125,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
id.Should().NotBeNullOrEmpty(); id.Should().NotBeNullOrEmpty();
Mocker.GetMock<ITransmissionProxy>() Mocker.GetMock<ITransmissionProxy>()
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), null, It.IsAny<TransmissionSettings>()), Times.Once()); .Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), null, It.IsAny<TransmissionSettings>()), Times.Once());
} }
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")] [TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
@ -404,6 +220,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
items.First().Status.Should().Be(DownloadItemStatus.Downloading); items.First().Status.Should().Be(DownloadItemStatus.Downloading);
} }
[Test]
public void should_exclude_items_not_in_TvDirectory() public void should_exclude_items_not_in_TvDirectory()
{ {
GivenTvDirectory(); GivenTvDirectory();
@ -444,13 +261,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
[TestCase("2.84+ ()")] [TestCase("2.84+ ()")]
[TestCase("2.84 (other info)")] [TestCase("2.84 (other info)")]
[TestCase("2.84 (2.84)")] [TestCase("2.84 (2.84)")]
public void should_version_should_only_check_version_number(string version) public void should_only_check_version_number(string version)
{ {
Mocker.GetMock<ITransmissionProxy>() Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.GetVersion(It.IsAny<TransmissionSettings>())) .Setup(s => s.GetClientVersion(It.IsAny<TransmissionSettings>()))
.Returns(version); .Returns(version);
Subject.Test(); Subject.Test().IsValid.Should().BeTrue();
} }
[TestCase(-1)] // Infinite/Unknown [TestCase(-1)] // Infinite/Unknown

View File

@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Transmission;
using NzbDrone.Core.MediaFiles.TorrentInfo;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
{
public abstract class TransmissionFixtureBase<TClient> : DownloadClientFixtureBase<TClient>
where TClient : class, IDownloadClient
{
protected TransmissionSettings _settings;
protected TransmissionTorrent _queued;
protected TransmissionTorrent _downloading;
protected TransmissionTorrent _failed;
protected TransmissionTorrent _completed;
protected TransmissionTorrent _magnet;
protected Dictionary<string, object> _transmissionConfigItems;
[SetUp]
public void Setup()
{
_settings = new TransmissionSettings
{
Host = "127.0.0.1",
Port = 2222,
Username = "admin",
Password = "pass"
};
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = _settings;
_queued = new TransmissionTorrent
{
HashString = "HASH",
IsFinished = false,
Status = TransmissionTorrentStatus.Queued,
Name = _title,
TotalSize = 1000,
LeftUntilDone = 1000,
DownloadDir = "somepath"
};
_downloading = new TransmissionTorrent
{
HashString = "HASH",
IsFinished = false,
Status = TransmissionTorrentStatus.Downloading,
Name = _title,
TotalSize = 1000,
LeftUntilDone = 100,
DownloadDir = "somepath"
};
_failed = new TransmissionTorrent
{
HashString = "HASH",
IsFinished = false,
Status = TransmissionTorrentStatus.Stopped,
Name = _title,
TotalSize = 1000,
LeftUntilDone = 100,
ErrorString = "Error",
DownloadDir = "somepath"
};
_completed = new TransmissionTorrent
{
HashString = "HASH",
IsFinished = true,
Status = TransmissionTorrentStatus.Stopped,
Name = _title,
TotalSize = 1000,
LeftUntilDone = 0,
DownloadDir = "somepath"
};
_magnet = new TransmissionTorrent
{
HashString = "HASH",
IsFinished = false,
Status = TransmissionTorrentStatus.Downloading,
Name = _title,
TotalSize = 0,
LeftUntilDone = 100,
DownloadDir = "somepath"
};
Mocker.GetMock<ITorrentFileInfoReader>()
.Setup(s => s.GetHashFromTorrentFile(It.IsAny<byte[]>()))
.Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951");
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[0]));
_transmissionConfigItems = new Dictionary<string, object>();
_transmissionConfigItems.Add("download-dir", @"C:/Downloads/Finished/transmission");
_transmissionConfigItems.Add("incomplete-dir", null);
_transmissionConfigItems.Add("incomplete-dir-enabled", false);
Mocker.GetMock<ITransmissionProxy>()
.Setup(v => v.GetConfig(It.IsAny<TransmissionSettings>()))
.Returns(_transmissionConfigItems);
}
protected void GivenTvCategory()
{
_settings.TvCategory = "sonarr";
}
protected void GivenTvDirectory()
{
_settings.TvDirectory = @"C:/Downloads/Finished/sonarr";
}
protected void GivenFailedDownload()
{
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TransmissionSettings>()))
.Throws<InvalidOperationException>();
}
protected void GivenSuccessfulDownload()
{
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new byte[1000]));
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TransmissionSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.AddTorrentFromData(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<TransmissionSettings>()))
.Callback(PrepareClientToReturnQueuedItem);
}
protected virtual void GivenTorrents(List<TransmissionTorrent> torrents)
{
if (torrents == null)
{
torrents = new List<TransmissionTorrent>();
}
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.GetTorrents(It.IsAny<TransmissionSettings>()))
.Returns(torrents);
}
protected void PrepareClientToReturnQueuedItem()
{
GivenTorrents(new List<TransmissionTorrent>
{
_queued
});
}
protected void PrepareClientToReturnDownloadingItem()
{
GivenTorrents(new List<TransmissionTorrent>
{
_downloading
});
}
protected void PrepareClientToReturnFailedItem()
{
GivenTorrents(new List<TransmissionTorrent>
{
_failed
});
}
protected void PrepareClientToReturnCompletedItem()
{
GivenTorrents(new List<TransmissionTorrent>
{
_completed
});
}
protected void PrepareClientToReturnMagnetItem()
{
GivenTorrents(new List<TransmissionTorrent>
{
_magnet
});
}
}
}

View File

@ -0,0 +1,315 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Transmission;
using NzbDrone.Core.Download.Clients.Vuze;
using NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
{
[TestFixture]
public class VuzeFixture : TransmissionFixtureBase<Vuze>
{
[Test]
public void queued_item_should_have_required_properties()
{
PrepareClientToReturnQueuedItem();
var item = Subject.GetItems().Single();
VerifyQueued(item);
}
[Test]
public void downloading_item_should_have_required_properties()
{
PrepareClientToReturnDownloadingItem();
var item = Subject.GetItems().Single();
VerifyDownloading(item);
}
[Test]
public void failed_item_should_have_required_properties()
{
PrepareClientToReturnFailedItem();
var item = Subject.GetItems().Single();
VerifyWarning(item);
}
[Test]
public void completed_download_should_have_required_properties()
{
PrepareClientToReturnCompletedItem();
var item = Subject.GetItems().Single();
VerifyCompleted(item);
}
[Test]
public void magnet_download_should_not_return_the_item()
{
PrepareClientToReturnMagnetItem();
Subject.GetItems().Count().Should().Be(0);
}
[Test]
public void Download_should_return_unique_id()
{
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
}
[Test]
public void Download_with_TvDirectory_should_force_directory()
{
GivenTvDirectory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<ITransmissionProxy>()
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/sonarr", It.IsAny<TransmissionSettings>()), Times.Once());
}
[Test]
public void Download_with_category_should_force_directory()
{
GivenTvCategory();
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<ITransmissionProxy>()
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny<TransmissionSettings>()), Times.Once());
}
[Test]
public void Download_with_category_should_not_have_double_slashes()
{
GivenTvCategory();
GivenSuccessfulDownload();
_transmissionConfigItems["download-dir"] += "/";
var remoteEpisode = CreateRemoteEpisode();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<ITransmissionProxy>()
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny<TransmissionSettings>()), Times.Once());
}
[Test]
public void Download_without_TvDirectory_and_Category_should_use_default()
{
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<ITransmissionProxy>()
.Verify(v => v.AddTorrentFromData(It.IsAny<byte[]>(), null, It.IsAny<TransmissionSettings>()), Times.Once());
}
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash)
{
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
remoteEpisode.Release.DownloadUrl = magnetUrl;
var id = Subject.Download(remoteEpisode);
id.Should().Be(expectedHash);
}
[TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)]
public void GetItems_should_return_queued_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus)
{
_queued.Status = apiStatus;
PrepareClientToReturnQueuedItem();
var item = Subject.GetItems().Single();
item.Status.Should().Be(expectedItemStatus);
}
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)]
[TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)]
public void GetItems_should_return_downloading_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus)
{
_downloading.Status = apiStatus;
PrepareClientToReturnDownloadingItem();
var item = Subject.GetItems().Single();
item.Status.Should().Be(expectedItemStatus);
}
[TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, false)]
[TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, true)]
[TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, true)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, true)]
[TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed, true)]
[TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed, true)]
public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly)
{
_completed.Status = apiStatus;
PrepareClientToReturnCompletedItem();
var item = Subject.GetItems().Single();
item.Status.Should().Be(expectedItemStatus);
item.IsReadOnly.Should().Be(expectedReadOnly);
}
[Test]
public void should_return_status_with_outputdirs()
{
var result = Subject.GetStatus();
result.IsLocalhost.Should().BeTrue();
result.OutputRootFolders.Should().NotBeNull();
result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\transmission");
}
[Test]
public void should_exclude_items_not_in_category()
{
GivenTvCategory();
_downloading.DownloadDir = @"C:/Downloads/Finished/transmission/sonarr";
GivenTorrents(new List<TransmissionTorrent>
{
_downloading,
_queued
});
var items = Subject.GetItems().ToList();
items.Count.Should().Be(1);
items.First().Status.Should().Be(DownloadItemStatus.Downloading);
}
[Test]
public void should_exclude_items_not_in_TvDirectory()
{
GivenTvDirectory();
_downloading.DownloadDir = @"C:/Downloads/Finished/sonarr/subdir";
GivenTorrents(new List<TransmissionTorrent>
{
_downloading,
_queued
});
var items = Subject.GetItems().ToList();
items.Count.Should().Be(1);
items.First().Status.Should().Be(DownloadItemStatus.Downloading);
}
[Test]
public void should_fix_forward_slashes()
{
WindowsOnly();
_downloading.DownloadDir = @"C:/Downloads/Finished/transmission/" + _title;
GivenTorrents(new List<TransmissionTorrent>
{
_downloading
});
var items = Subject.GetItems().ToList();
items.Should().HaveCount(1);
items.First().OutputPath.Should().Be(@"C:\Downloads\Finished\transmission\" + _title);
}
[TestCase(-1)] // Infinite/Unknown
[TestCase(-2)] // Magnet Downloading
public void should_ignore_negative_eta(int eta)
{
_completed.Eta = eta;
PrepareClientToReturnCompletedItem();
var item = Subject.GetItems().Single();
item.RemainingTime.Should().NotHaveValue();
}
[TestCase("14")]
[TestCase("15")]
[TestCase("20")]
public void should_only_check_protocol_version_number(string version)
{
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.GetProtocolVersion(It.IsAny<TransmissionSettings>()))
.Returns(version);
Subject.Test().IsValid.Should().BeTrue();
}
[TestCase("")]
[TestCase("10")]
[TestCase("foo")]
public void should_fail_with_unsupported_protocol_version(string version)
{
Mocker.GetMock<ITransmissionProxy>()
.Setup(s => s.GetProtocolVersion(It.IsAny<TransmissionSettings>()))
.Returns(version);
Subject.Test().IsValid.Should().BeFalse();
}
[Test]
public void should_have_correct_output_directory()
{
WindowsOnly();
_downloading.DownloadDir = @"C:/Downloads/" + _title;
GivenTorrents(new List<TransmissionTorrent>
{
_downloading
});
var items = Subject.GetItems().ToList();
items.Should().HaveCount(1);
items.First().OutputPath.Should().Be(@"C:\Downloads\" + _title);
}
}
}

View File

@ -171,7 +171,9 @@
<Compile Include="Download\DownloadClientTests\QBittorrentTests\QBittorrentFixture.cs" /> <Compile Include="Download\DownloadClientTests\QBittorrentTests\QBittorrentFixture.cs" />
<Compile Include="Download\DownloadClientTests\SabnzbdTests\SabnzbdFixture.cs" /> <Compile Include="Download\DownloadClientTests\SabnzbdTests\SabnzbdFixture.cs" />
<Compile Include="Download\DownloadClientTests\TransmissionTests\TransmissionFixture.cs" /> <Compile Include="Download\DownloadClientTests\TransmissionTests\TransmissionFixture.cs" />
<Compile Include="Download\DownloadClientTests\TransmissionTests\TransmissionFixtureBase.cs" />
<Compile Include="Download\DownloadClientTests\UTorrentTests\UTorrentFixture.cs" /> <Compile Include="Download\DownloadClientTests\UTorrentTests\UTorrentFixture.cs" />
<Compile Include="Download\DownloadClientTests\VuzeTests\VuzeFixture.cs" />
<Compile Include="Download\DownloadServiceFixture.cs" /> <Compile Include="Download\DownloadServiceFixture.cs" />
<Compile Include="Download\FailedDownloadServiceFixture.cs" /> <Compile Include="Download\FailedDownloadServiceFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\PendingReleaseServiceFixture.cs" /> <Compile Include="Download\Pending\PendingReleaseServiceTests\PendingReleaseServiceFixture.cs" />

View File

@ -1,25 +1,17 @@
using System; using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NLog; using NLog;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Validation;
using System.Net;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.RemotePathMappings;
namespace NzbDrone.Core.Download.Clients.Transmission namespace NzbDrone.Core.Download.Clients.Transmission
{ {
public class Transmission : TorrentClientBase<TransmissionSettings> public class Transmission : TransmissionBase
{ {
private readonly ITransmissionProxy _proxy;
public Transmission(ITransmissionProxy proxy, public Transmission(ITransmissionProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader, ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient, IHttpClient httpClient,
@ -27,59 +19,25 @@ namespace NzbDrone.Core.Download.Clients.Transmission
IDiskProvider diskProvider, IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService, IRemotePathMappingService remotePathMappingService,
Logger logger) Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
{ {
_proxy = proxy;
} }
protected override ValidationFailure ValidateVersion()
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
{ {
_proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings); var versionString = _proxy.GetClientVersion(Settings);
var isRecentEpisode = remoteEpisode.IsRecentEpisode(); _logger.Debug("Transmission version information: {0}", versionString);
if (isRecentEpisode && Settings.RecentTvPriority == (int)TransmissionPriority.First || var versionResult = Regex.Match(versionString, @"(?<!\(|(\d|\.)+)(\d|\.)+(?!\)|(\d|\.)+)").Value;
!isRecentEpisode && Settings.OlderTvPriority == (int)TransmissionPriority.First) var version = Version.Parse(versionResult);
if (version < new Version(2, 40))
{ {
_proxy.MoveTorrentToTopInQueue(hash, Settings); return new ValidationFailure(string.Empty, "Transmission version not supported, should be 2.40 or higher.");
} }
return hash; return null;
}
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
{
_proxy.AddTorrentFromData(fileContent, GetDownloadDirectory(), Settings);
var isRecentEpisode = remoteEpisode.IsRecentEpisode();
if (isRecentEpisode && Settings.RecentTvPriority == (int)TransmissionPriority.First ||
!isRecentEpisode && Settings.OlderTvPriority == (int)TransmissionPriority.First)
{
_proxy.MoveTorrentToTopInQueue(hash, Settings);
}
return hash;
}
private string GetDownloadDirectory()
{
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
{
return Settings.TvDirectory;
}
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
var config = _proxy.GetConfig(Settings);
var destDir = (string)config.GetValueOrDefault("download-dir");
return string.Format("{0}/{1}", destDir.TrimEnd('/'), Settings.TvCategory);
}
else
{
return null;
}
} }
public override string Name public override string Name
@ -89,175 +47,5 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return "Transmission"; return "Transmission";
} }
} }
public override IEnumerable<DownloadClientItem> GetItems()
{
List<TransmissionTorrent> torrents;
try
{
torrents = _proxy.GetTorrents(Settings);
}
catch (DownloadClientException ex)
{
_logger.Error(ex, ex.Message);
return Enumerable.Empty<DownloadClientItem>();
}
var items = new List<DownloadClientItem>();
foreach (var torrent in torrents)
{
// If totalsize == 0 the torrent is a magnet downloading metadata
if (torrent.TotalSize == 0)
continue;
var outputPath = new OsPath(torrent.DownloadDir);
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
{
if (!new OsPath(Settings.TvDirectory).Contains(outputPath)) continue;
}
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
var directories = outputPath.FullPath.Split('\\', '/');
if (!directories.Contains(string.Format("{0}", Settings.TvCategory))) continue;
}
outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath);
var item = new DownloadClientItem();
item.DownloadId = torrent.HashString.ToUpper();
item.Category = Settings.TvCategory;
item.Title = torrent.Name;
item.DownloadClient = Definition.Name;
item.OutputPath = outputPath + torrent.Name;
item.TotalSize = torrent.TotalSize;
item.RemainingSize = torrent.LeftUntilDone;
if (torrent.Eta >= 0)
{
item.RemainingTime = TimeSpan.FromSeconds(torrent.Eta);
}
if (!torrent.ErrorString.IsNullOrWhiteSpace())
{
item.Status = DownloadItemStatus.Warning;
item.Message = torrent.ErrorString;
}
else if (torrent.Status == TransmissionTorrentStatus.Seeding || torrent.Status == TransmissionTorrentStatus.SeedingWait)
{
item.Status = DownloadItemStatus.Completed;
}
else if (torrent.IsFinished && torrent.Status != TransmissionTorrentStatus.Check && torrent.Status != TransmissionTorrentStatus.CheckWait)
{
item.Status = DownloadItemStatus.Completed;
}
else if (torrent.Status == TransmissionTorrentStatus.Queued)
{
item.Status = DownloadItemStatus.Queued;
}
else
{
item.Status = DownloadItemStatus.Downloading;
}
item.IsReadOnly = torrent.Status != TransmissionTorrentStatus.Stopped;
items.Add(item);
}
return items;
}
public override void RemoveItem(string downloadId, bool deleteData)
{
_proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings);
}
public override DownloadClientStatus GetStatus()
{
var config = _proxy.GetConfig(Settings);
var destDir = config.GetValueOrDefault("download-dir") as string;
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
destDir = string.Format("{0}/.{1}", destDir, Settings.TvCategory);
}
return new DownloadClientStatus
{
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost",
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) }
};
}
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestConnection());
if (failures.Any()) return;
failures.AddIfNotNull(TestGetTorrents());
}
private ValidationFailure TestConnection()
{
try
{
var versionString = _proxy.GetVersion(Settings);
_logger.Debug("Transmission version information: {0}", versionString);
var versionResult = Regex.Match(versionString, @"(?<!\(|(\d|\.)+)(\d|\.)+(?!\)|(\d|\.)+)").Value;
var version = Version.Parse(versionResult);
if (version < new Version(2, 40))
{
return new ValidationFailure(string.Empty, "Transmission version not supported, should be 2.40 or higher.");
}
}
catch (DownloadClientAuthenticationException ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Username", "Authentication failure")
{
DetailedDescription = "Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing Transmission by WhiteList limitations in the Transmission configuration."
};
}
catch (WebException ex)
{
_logger.Error(ex, ex.Message);
if (ex.Status == WebExceptionStatus.ConnectFailure)
{
return new NzbDroneValidationFailure("Host", "Unable to connect")
{
DetailedDescription = "Please verify the hostname and port."
};
}
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
}
catch (Exception ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
}
return null;
}
private ValidationFailure TestGetTorrents()
{
try
{
_proxy.GetTorrents(Settings);
}
catch (Exception ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message);
}
return null;
}
} }
} }

View File

@ -0,0 +1,250 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Download.Clients.Transmission
{
public abstract class TransmissionBase : TorrentClientBase<TransmissionSettings>
{
protected readonly ITransmissionProxy _proxy;
public TransmissionBase(ITransmissionProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
{
_proxy = proxy;
}
public override IEnumerable<DownloadClientItem> GetItems()
{
List<TransmissionTorrent> torrents;
try
{
torrents = _proxy.GetTorrents(Settings);
}
catch (DownloadClientException ex)
{
_logger.Error(ex, ex.Message);
return Enumerable.Empty<DownloadClientItem>();
}
var items = new List<DownloadClientItem>();
foreach (var torrent in torrents)
{
// If totalsize == 0 the torrent is a magnet downloading metadata
if (torrent.TotalSize == 0) continue;
var outputPath = new OsPath(torrent.DownloadDir);
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
{
if (!new OsPath(Settings.TvDirectory).Contains(outputPath)) continue;
}
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
var directories = outputPath.FullPath.Split('\\', '/');
if (!directories.Contains(Settings.TvCategory)) continue;
}
outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath);
var item = new DownloadClientItem();
item.DownloadId = torrent.HashString.ToUpper();
item.Category = Settings.TvCategory;
item.Title = torrent.Name;
item.DownloadClient = Definition.Name;
item.OutputPath = GetOutputPath(outputPath, torrent);
item.TotalSize = torrent.TotalSize;
item.RemainingSize = torrent.LeftUntilDone;
if (torrent.Eta >= 0)
{
item.RemainingTime = TimeSpan.FromSeconds(torrent.Eta);
}
if (!torrent.ErrorString.IsNullOrWhiteSpace())
{
item.Status = DownloadItemStatus.Warning;
item.Message = torrent.ErrorString;
}
else if (torrent.Status == TransmissionTorrentStatus.Seeding ||
torrent.Status == TransmissionTorrentStatus.SeedingWait)
{
item.Status = DownloadItemStatus.Completed;
}
else if (torrent.IsFinished && torrent.Status != TransmissionTorrentStatus.Check &&
torrent.Status != TransmissionTorrentStatus.CheckWait)
{
item.Status = DownloadItemStatus.Completed;
}
else if (torrent.Status == TransmissionTorrentStatus.Queued)
{
item.Status = DownloadItemStatus.Queued;
}
else
{
item.Status = DownloadItemStatus.Downloading;
}
item.IsReadOnly = torrent.Status != TransmissionTorrentStatus.Stopped;
items.Add(item);
}
return items;
}
public override void RemoveItem(string downloadId, bool deleteData)
{
_proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings);
}
public override DownloadClientStatus GetStatus()
{
var config = _proxy.GetConfig(Settings);
var destDir = config.GetValueOrDefault("download-dir") as string;
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
destDir = string.Format("{0}/.{1}", destDir, Settings.TvCategory);
}
return new DownloadClientStatus
{
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost",
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) }
};
}
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
{
_proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings);
var isRecentEpisode = remoteEpisode.IsRecentEpisode();
if (isRecentEpisode && Settings.RecentTvPriority == (int)TransmissionPriority.First ||
!isRecentEpisode && Settings.OlderTvPriority == (int)TransmissionPriority.First)
{
_proxy.MoveTorrentToTopInQueue(hash, Settings);
}
return hash;
}
protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
{
_proxy.AddTorrentFromData(fileContent, GetDownloadDirectory(), Settings);
var isRecentEpisode = remoteEpisode.IsRecentEpisode();
if (isRecentEpisode && Settings.RecentTvPriority == (int)TransmissionPriority.First ||
!isRecentEpisode && Settings.OlderTvPriority == (int)TransmissionPriority.First)
{
_proxy.MoveTorrentToTopInQueue(hash, Settings);
}
return hash;
}
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestConnection());
if (failures.Any()) return;
failures.AddIfNotNull(TestGetTorrents());
}
protected virtual OsPath GetOutputPath(OsPath outputPath, TransmissionTorrent torrent)
{
return outputPath + torrent.Name;
}
protected string GetDownloadDirectory()
{
if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
{
return Settings.TvDirectory;
}
else if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
var config = _proxy.GetConfig(Settings);
var destDir = (string)config.GetValueOrDefault("download-dir");
return string.Format("{0}/{1}", destDir.TrimEnd('/'), Settings.TvCategory);
}
else
{
return null;
}
}
protected ValidationFailure TestConnection()
{
try
{
return ValidateVersion();
}
catch (DownloadClientAuthenticationException ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Username", "Authentication failure")
{
DetailedDescription = string.Format("Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {0} by WhiteList limitations in the {0} configuration.", Name)
};
}
catch (WebException ex)
{
_logger.Error(ex, ex.Message);
if (ex.Status == WebExceptionStatus.ConnectFailure)
{
return new NzbDroneValidationFailure("Host", "Unable to connect")
{
DetailedDescription = "Please verify the hostname and port."
};
}
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
}
catch (Exception ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
}
}
protected abstract ValidationFailure ValidateVersion();
private ValidationFailure TestGetTorrents()
{
try
{
_proxy.GetTorrents(Settings);
}
catch (Exception ex)
{
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message);
}
return null;
}
}
}

View File

@ -19,7 +19,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission
void AddTorrentFromData(byte[] torrentData, string downloadDirectory, TransmissionSettings settings); void AddTorrentFromData(byte[] torrentData, string downloadDirectory, TransmissionSettings settings);
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, TransmissionSettings settings); void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, TransmissionSettings settings);
Dictionary<string, object> GetConfig(TransmissionSettings settings); Dictionary<string, object> GetConfig(TransmissionSettings settings);
string GetVersion(TransmissionSettings settings); string GetProtocolVersion(TransmissionSettings settings);
string GetClientVersion(TransmissionSettings settings);
void RemoveTorrent(string hash, bool removeData, TransmissionSettings settings); void RemoveTorrent(string hash, bool removeData, TransmissionSettings settings);
void MoveTorrentToTopInQueue(string hashString, TransmissionSettings settings); void MoveTorrentToTopInQueue(string hashString, TransmissionSettings settings);
} }
@ -94,9 +95,17 @@ namespace NzbDrone.Core.Download.Clients.Transmission
ProcessRequest("torrent-set", arguments, settings); ProcessRequest("torrent-set", arguments, settings);
} }
public string GetVersion(TransmissionSettings settings) public string GetProtocolVersion(TransmissionSettings settings)
{
var config = GetConfig(settings);
var version = config["rpc-version"];
return version.ToString();
}
public string GetClientVersion(TransmissionSettings settings)
{ {
// Gets the transmission version.
var config = GetConfig(settings); var config = GetConfig(settings);
var version = config["version"]; var version = config["version"];

View File

@ -0,0 +1,60 @@
using System;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Transmission;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.RemotePathMappings;
namespace NzbDrone.Core.Download.Clients.Vuze
{
public class Vuze : TransmissionBase
{
private const int MINIMUM_SUPPORTED_PROTOCOL_VERSION = 14;
public Vuze(ITransmissionProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader,
IHttpClient httpClient,
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
Logger logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
{
}
protected override OsPath GetOutputPath(OsPath outputPath, TransmissionTorrent torrent)
{
_logger.Debug("Vuze output directory: {0}", outputPath);
return outputPath;
}
protected override ValidationFailure ValidateVersion()
{
var versionString = _proxy.GetProtocolVersion(Settings);
_logger.Debug("Vuze protocol version information: {0}", versionString);
int version;
if (!int.TryParse(versionString, out version) || version < MINIMUM_SUPPORTED_PROTOCOL_VERSION)
{
{
return new ValidationFailure(string.Empty, "Protocol version not supported, use Vuze 5.0.0.0 or higher with Vuze Web Remote plugin.");
}
}
return null;
}
public override string Name
{
get
{
return "Vuze";
}
}
}
}

View File

@ -425,6 +425,7 @@
<Compile Include="Download\Clients\rTorrent\RTorrentSettings.cs" /> <Compile Include="Download\Clients\rTorrent\RTorrentSettings.cs" />
<Compile Include="Download\Clients\rTorrent\RTorrentTorrent.cs" /> <Compile Include="Download\Clients\rTorrent\RTorrentTorrent.cs" />
<Compile Include="Download\Clients\Transmission\Transmission.cs" /> <Compile Include="Download\Clients\Transmission\Transmission.cs" />
<Compile Include="Download\Clients\Transmission\TransmissionBase.cs" />
<Compile Include="Download\Clients\Transmission\TransmissionException.cs" /> <Compile Include="Download\Clients\Transmission\TransmissionException.cs" />
<Compile Include="Download\Clients\Transmission\TransmissionProxy.cs" /> <Compile Include="Download\Clients\Transmission\TransmissionProxy.cs" />
<Compile Include="Download\Clients\Transmission\TransmissionResponse.cs" /> <Compile Include="Download\Clients\Transmission\TransmissionResponse.cs" />
@ -441,6 +442,7 @@
<Compile Include="Download\Clients\uTorrent\UTorrentSettings.cs" /> <Compile Include="Download\Clients\uTorrent\UTorrentSettings.cs" />
<Compile Include="Download\Clients\uTorrent\UTorrentTorrent.cs" /> <Compile Include="Download\Clients\uTorrent\UTorrentTorrent.cs" />
<Compile Include="Download\Clients\uTorrent\UTorrentTorrentStatus.cs" /> <Compile Include="Download\Clients\uTorrent\UTorrentTorrentStatus.cs" />
<Compile Include="Download\Clients\Vuze\Vuze.cs" />
<Compile Include="Download\CompletedDownloadService.cs" /> <Compile Include="Download\CompletedDownloadService.cs" />
<Compile Include="Download\DownloadEventHub.cs" /> <Compile Include="Download\DownloadEventHub.cs" />
<Compile Include="Download\TrackedDownloads\DownloadMonitoringService.cs" /> <Compile Include="Download\TrackedDownloads\DownloadMonitoringService.cs" />