diff --git a/src/NzbDrone.Core.Test/IndexerTests/TitansOfTvTests/TitansOfTvFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/TitansOfTvTests/TitansOfTvFixture.cs index ea2e74441..a25095bfe 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TitansOfTvTests/TitansOfTvFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TitansOfTvTests/TitansOfTvFixture.cs @@ -1,14 +1,14 @@ -using Moq; +using System; +using System.Linq; +using FluentAssertions; +using Moq; using NUnit.Framework; using NzbDrone.Common.Http; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.TitansOfTv; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; -using System; -using System.Linq; -using FluentAssertions; -using NzbDrone.Core.Indexers.TitansOfTv; namespace NzbDrone.Core.Test.IndexerTests.TitansOfTvTests { @@ -21,12 +21,12 @@ namespace NzbDrone.Core.Test.IndexerTests.TitansOfTvTests Subject.Definition = new IndexerDefinition { Name = "TitansOfTV", - Settings = new TitansOfTvSettings { ApiKey = "abc", BaseUrl = "https://titansof.tv/api/torrents" } + Settings = new TitansOfTvSettings { ApiKey = "abc", BaseUrl = "https://titansof.tv/api" } }; } [Test] - public void should_parse_recent_feed_from_BroadcastheNet() + public void should_parse_recent_feed_from_TitansOfTv() { var recentFeed = ReadAllText(@"Files/Indexers/TitansOfTv/RecentFeed.json"); @@ -59,7 +59,8 @@ namespace NzbDrone.Core.Test.IndexerTests.TitansOfTvTests private void VerifyBackOff() { - // TODO How to detect (and implement) back-off logic. + Mocker.GetMock() + .Verify(v => v.RecordFailure(It.IsAny(), It.IsAny()), Times.Once()); } [Test] diff --git a/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTv.cs b/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTv.cs index 826f0d394..0cb780f51 100644 --- a/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTv.cs +++ b/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTv.cs @@ -8,38 +8,30 @@ namespace NzbDrone.Core.Indexers.TitansOfTv { public class TitansOfTv : HttpIndexerBase { - public TitansOfTv(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) - : base(httpClient, configService, parsingService, logger) + public TitansOfTv(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, indexerStatusService, configService, parsingService, logger) { } public override string Name { - get - { - return "Titans of TV"; - } + get { return "Titans of TV"; } } - public override DownloadProtocol Protocol - { - get - { - return DownloadProtocol.Torrent; - } - } + public override DownloadProtocol Protocol { get { return DownloadProtocol.Torrent; } } + public override bool SupportsRss { get { return true; } } + public override bool SupportsSearch { get { return true; } } + public override int PageSize { get { return 100; } } public override IIndexerRequestGenerator GetRequestGenerator() { - return new TitansOfTvRequestGenerator() { Settings = Settings }; + return new TitansOfTvRequestGenerator() { Settings = Settings, PageSize = PageSize }; } public override IParseIndexerResponse GetParser() { return new TitansOfTvParser(); } - - public override Boolean SupportsSearch { get { return true; } } } } diff --git a/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvApiResult.cs b/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvApiResult.cs index 67112dd89..d2ecf46bd 100644 --- a/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvApiResult.cs +++ b/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvApiResult.cs @@ -3,16 +3,26 @@ using System.Collections.Generic; namespace NzbDrone.Core.Indexers.TitansOfTv { - public class Result + public class TitansOfTvApiResult + { + public string code { get; set; } + public int http_code { get; set; } + public int total { get; set; } + public int offset { get; set; } + public int limit { get; set; } + public List results { get; set; } + } + + public class TitansOfTvTorrent { public string id { get; set; } public string series_id { get; set; } public string episode_id { get; set; } public string season_id { get; set; } - public string seeders { get; set; } - public string leechers { get; set; } - public string size { get; set; } - public string snatched { get; set; } + public int? seeders { get; set; } + public int? leechers { get; set; } + public long size { get; set; } + public int? snatched { get; set; } public int user_id { get; set; } public string anonymous { get; set; } public string container { get; set; } @@ -29,17 +39,10 @@ namespace NzbDrone.Core.Indexers.TitansOfTv public string episode { get; set; } public string series { get; set; } public string network { get; set; } + public string mediainfo { get; set; } public string download { get; set; } + public string additional { get; set; } public string episodeUrl { get; set; } - } - - public class ApiResult - { - public string code { get; set; } - public int http_code { get; set; } - public int total { get; set; } - public int offset { get; set; } - public int limit { get; set; } - public List results { get; set; } + public string commentUrl { get; set; } } } diff --git a/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvParser.cs b/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvParser.cs index 8e833c0d2..a0ba04ba7 100644 --- a/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvParser.cs +++ b/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvParser.cs @@ -1,8 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net; using System.Text.RegularExpressions; -using Newtonsoft.Json; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Serializer; using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.Parser.Model; @@ -33,20 +33,24 @@ namespace NzbDrone.Core.Indexers.TitansOfTv } var content = indexerResponse.HttpResponse.Content; - var parsed = JsonConvert.DeserializeObject(content); + var parsed = Json.Deserialize(content); var protocol = indexerResponse.HttpRequest.Url.Scheme + ":"; foreach (var parsedItem in parsed.results) { var release = new TorrentInfo(); - release.Guid = String.Format("ToTV-{0}", parsedItem.id); + release.Guid = string.Format("ToTV-{0}", parsedItem.id); release.DownloadUrl = RegexProtocol.Replace(parsedItem.download, protocol); release.InfoUrl = RegexProtocol.Replace(parsedItem.episodeUrl, protocol); + if (parsedItem.commentUrl.IsNotNullOrWhiteSpace()) + { + release.CommentUrl = RegexProtocol.Replace(parsedItem.commentUrl, protocol); + } release.DownloadProtocol = DownloadProtocol.Torrent; release.Title = parsedItem.release_name; - release.Size = Convert.ToInt64(parsedItem.size); - release.Seeders = Convert.ToInt32(parsedItem.seeders); - release.Peers = Convert.ToInt32(parsedItem.leechers) + release.Seeders; + release.Size = parsedItem.size; + release.Seeders = parsedItem.seeders; + release.Peers = parsedItem.leechers + release.Seeders; release.PublishDate = parsedItem.created_at; results.Add(release); } diff --git a/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvRequestGenerator.cs b/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvRequestGenerator.cs index 6d2120e0f..a7f665087 100644 --- a/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvRequestGenerator.cs @@ -1,111 +1,119 @@ using System; using System.Collections.Generic; using System.Globalization; +using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.IndexerSearch.Definitions; namespace NzbDrone.Core.Indexers.TitansOfTv { public class TitansOfTvRequestGenerator : IIndexerRequestGenerator { + public int MaxPages { get; set; } + public int PageSize { get; set; } public TitansOfTvSettings Settings { get; set; } + + public TitansOfTvRequestGenerator() + { + MaxPages = 30; + PageSize = 100; + } public IList> GetRecentRequests() { var pageableRequests = new List>(); - var innerList = new List(); - var httpRequest = BuildHttpRequest(GetBaseUrl()); - innerList.Add(new IndexerRequest(httpRequest)); - pageableRequests.Add(innerList); + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages)); return pageableRequests; } - private HttpRequest BuildHttpRequest(string url) + public IList> GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria) { - var httpRequest = new HttpRequest(url, HttpAccept.Json); - httpRequest.Headers["X-Authorization"] = Settings.ApiKey; - - return httpRequest; - } - - public IList> GetSearchRequests(IndexerSearch.Definitions.SingleEpisodeSearchCriteria searchCriteria) - { - var url = GetBaseUrl() + "&series_id={series}&episode={episode}"; - var requests = new List>(); - var innerList = new List(); - requests.Add(innerList); + var pageableRequests = new List>(); - var httpRequest = BuildHttpRequest(url); - var episodeString = String.Format("S{0:00}E{1:00}", searchCriteria.SeasonNumber, searchCriteria.EpisodeNumber); - httpRequest.AddSegment("series", searchCriteria.Series.TvdbId.ToString(CultureInfo.InvariantCulture)); - httpRequest.AddSegment("episode", episodeString); + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, + series_id: searchCriteria.Series.TvdbId, + episode: string.Format("S{0:00}E{1:00}", searchCriteria.SeasonNumber, searchCriteria.EpisodeNumber))); - var request = new IndexerRequest(httpRequest); - innerList.Add(request); + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, + series_id: searchCriteria.Series.TvdbId, + season: string.Format("Season {0:00}", searchCriteria.SeasonNumber))); - return requests; + return pageableRequests; } - public IList> GetSearchRequests(IndexerSearch.Definitions.SeasonSearchCriteria searchCriteria) + public IList> GetSearchRequests(SeasonSearchCriteria searchCriteria) { - var url = GetBaseUrl() + "&series_id={series}&season={season}"; - var requests = new List>(); - var innerList = new List(); - requests.Add(innerList); - - var httpRequest = BuildHttpRequest(url); - var seasonString = String.Format("Season {0:00}", searchCriteria.SeasonNumber); - httpRequest.AddSegment("series", searchCriteria.Series.TvdbId.ToString(CultureInfo.InvariantCulture)); - httpRequest.AddSegment("season", seasonString); - - var request = new IndexerRequest(httpRequest); - innerList.Add(request); + var pageableRequests = new List>(); - httpRequest = BuildHttpRequest(url); - seasonString = String.Format("Season {0}", searchCriteria.SeasonNumber); - httpRequest.AddSegment("series", searchCriteria.Series.TvdbId.ToString(CultureInfo.InvariantCulture)); - httpRequest.AddSegment("season", seasonString); + // TODO: Search for all episodes?!? - request = new IndexerRequest(httpRequest); - innerList.Add(request); + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, + series_id: searchCriteria.Series.TvdbId, + season: string.Format("Season {0:00}", searchCriteria.SeasonNumber))); - return requests; + return pageableRequests; } - public IList> GetSearchRequests(IndexerSearch.Definitions.DailyEpisodeSearchCriteria searchCriteria) + public IList> GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria) { - var url = GetBaseUrl() + "&series_id={series}&air_date={air_date}"; - var requests = new List>(); - var innerList = new List(); - - requests.Add(innerList); - - var httpRequest = BuildHttpRequest(url); - var airDate = searchCriteria.AirDate.ToString("yyyy-MM-dd"); - - httpRequest.AddSegment("series", searchCriteria.Series.TvdbId.ToString(CultureInfo.InvariantCulture)); - httpRequest.AddSegment("air_date", airDate); + var pageableRequests = new List>(); - var request = new IndexerRequest(httpRequest); - innerList.Add(request); + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, + series_id: searchCriteria.Series.TvdbId, + air_date: searchCriteria.AirDate)); - return requests; + return pageableRequests; } - public IList> GetSearchRequests(IndexerSearch.Definitions.AnimeEpisodeSearchCriteria searchCriteria) + public IList> GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria) { return new List>(); } - public IList> GetSearchRequests(IndexerSearch.Definitions.SpecialEpisodeSearchCriteria searchCriteria) + public IList> GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria) { return new List>(); } - private string GetBaseUrl() + private IEnumerable GetPagedRequests(int maxPages, int? series_id = null, string episode = null, string season = null, DateTime? air_date = null) { - return Settings.BaseUrl + "?limit=100"; + var pageSize = PageSize; + + if (pageSize == 0) + { + maxPages = 1; + pageSize = 100; + } + + for (var page = 0; page < maxPages; page++) + { + var request = new IndexerRequest(string.Format("{0}/torrents?offset={1}&limit={2}", Settings.BaseUrl.TrimEnd('/'), page * pageSize, pageSize), HttpAccept.Json); + request.HttpRequest.Headers.Add("X-Authorization", Settings.ApiKey); + + if (series_id.HasValue) + { + request.HttpRequest.AddQueryParam("series_id", series_id.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (season != null) + { + request.HttpRequest.AddQueryParam("season", season); + } + + if (episode != null) + { + request.HttpRequest.AddQueryParam("episode", episode); + } + + if (air_date.HasValue) + { + request.HttpRequest.AddQueryParam("air_date", air_date.Value.ToString("yyyy-MM-dd")); + } + + yield return request; + } } } } diff --git a/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvSettings.cs b/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvSettings.cs index 48eb3cbcf..013db49a7 100644 --- a/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvSettings.cs +++ b/src/NzbDrone.Core/Indexers/TitansOfTv/TitansOfTvSettings.cs @@ -5,8 +5,7 @@ using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.TitansOfTv { - - public class TitansOfTvSettingsValidator : AbstractValidator + public class TitansOfTvSettingsValidator : AbstractValidator { public TitansOfTvSettingsValidator() { @@ -20,7 +19,7 @@ namespace NzbDrone.Core.Indexers.TitansOfTv public TitansOfTvSettings() { - BaseUrl = "http://titansof.tv/api/torrents"; + BaseUrl = "http://titansof.tv/api"; } [FieldDefinition(0, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your API key will be sent to that host.")]