From 5e62c2335f81d299e81e31e84e56c16cf11cbb16 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sun, 7 Sep 2014 13:44:24 +0200 Subject: [PATCH] Fixed: Refactored the Indexer architecture to support non-rss indexers. --- src/NzbDrone.Common/Http/HttpAccept.cs | 26 ++ src/NzbDrone.Common/Http/HttpClient.cs | 4 +- src/NzbDrone.Common/Http/HttpException.cs | 6 + src/NzbDrone.Common/Http/HttpHeader.cs | 32 ++ src/NzbDrone.Common/Http/HttpRequest.cs | 10 +- src/NzbDrone.Common/Http/HttpResponse.cs | 34 +- src/NzbDrone.Common/NzbDrone.Common.csproj | 1 + src/NzbDrone.Core.Test/Files/RSS/fanzub.xml | 37 ++ .../RSS/{newznab.xml => newznab_nzb_su.xml} | 402 +++++++++--------- .../HealthCheck/Checks/IndexerCheckFixture.cs | 2 +- .../NzbSearchServiceFixture.cs | 22 +- .../AnimezbTests/AnimezbFixture.cs | 56 +++ .../IndexerTests/BasicRssParserFixture.cs | 4 +- .../IndexerTests/FanzubTests/FanzubFixture.cs | 53 +++ .../IndexerTests/IndexerServiceFixture.cs | 6 +- .../IndexerIntegrationTests.cs | 8 +- .../NewznabTests/NewznabFixture.cs | 57 +++ .../NewznabRequestGeneratorFixture.cs | 123 ++++++ .../OmgwtfnzbsTests/OmgwtfnzbsFixture.cs | 57 +++ .../IndexerTests/SeasonSearchFixture.cs | 74 ++-- .../IndexerTests/TestIndexer.cs | 38 ++ .../IndexerTests/TestIndexerSettings.cs | 17 + .../NzbDrone.Core.Test.csproj | 13 +- .../IndexerSearch/NzbSearchService.cs | 17 +- src/NzbDrone.Core/Indexers/Animezb/Animezb.cs | 93 +--- .../Indexers/Animezb/AnimezbParser.cs | 31 -- .../Animezb/AnimezbRequestGenerator.cs | 105 +++++ .../Indexers/BasicTorrentRssParser.cs | 47 -- .../Indexers/Exceptions/IndexerException.cs | 28 ++ .../SizeParsingException.cs | 2 +- src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs | 82 +--- .../Indexers/Fanzub/FanzubParser.cs | 31 -- .../Indexers/Fanzub/FanzubRequestGenerator.cs | 90 ++++ .../Indexers/FetchAndParseRssService.cs | 6 +- src/NzbDrone.Core/Indexers/HttpIndexerBase.cs | 229 ++++++++++ src/NzbDrone.Core/Indexers/IIndexer.cs | 21 +- .../Indexers/IIndexerRequestGenerator.cs | 18 + src/NzbDrone.Core/Indexers/IParseFeed.cs | 10 - .../Indexers/IProcessIndexerResponse.cs | 13 + src/NzbDrone.Core/Indexers/IndexerBase.cs | 92 ++-- .../Indexers/IndexerFetchService.cs | 197 --------- src/NzbDrone.Core/Indexers/IndexerRequest.cs | 60 +++ src/NzbDrone.Core/Indexers/Newznab/Newznab.cs | 185 +------- .../Indexers/Newznab/NewznabParser.cs | 95 ----- .../Indexers/Newznab/NewznabPreProcessor.cs | 35 -- .../Newznab/NewznabRequestGenerator.cs | 176 ++++++++ .../Indexers/Newznab/NewznabRssParser.cs | 112 +++++ .../Indexers/NewznabTestService.cs | 65 --- .../Indexers/Omgwtfnzbs/Omgwtfnzbs.cs | 82 +--- .../Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs | 106 +++++ ...tfnzbsParser.cs => OmgwtfnzbsRssParser.cs} | 16 +- .../Indexers/RssIndexerRequestGenerator.cs | 54 +++ src/NzbDrone.Core/Indexers/RssParser.cs | 235 ++++++++++ src/NzbDrone.Core/Indexers/RssParserBase.cs | 168 -------- src/NzbDrone.Core/Indexers/Wombles/Wombles.cs | 46 +- .../{WomblesParser.cs => WomblesRssParser.cs} | 8 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 29 +- 57 files changed, 2196 insertions(+), 1470 deletions(-) create mode 100644 src/NzbDrone.Common/Http/HttpAccept.cs create mode 100644 src/NzbDrone.Core.Test/Files/RSS/fanzub.xml rename src/NzbDrone.Core.Test/Files/RSS/{newznab.xml => newznab_nzb_su.xml} (82%) create mode 100644 src/NzbDrone.Core.Test/IndexerTests/AnimezbTests/AnimezbFixture.cs create mode 100644 src/NzbDrone.Core.Test/IndexerTests/FanzubTests/FanzubFixture.cs create mode 100644 src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs create mode 100644 src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs create mode 100644 src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs create mode 100644 src/NzbDrone.Core.Test/IndexerTests/TestIndexer.cs create mode 100644 src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs delete mode 100644 src/NzbDrone.Core/Indexers/Animezb/AnimezbParser.cs create mode 100644 src/NzbDrone.Core/Indexers/Animezb/AnimezbRequestGenerator.cs delete mode 100644 src/NzbDrone.Core/Indexers/BasicTorrentRssParser.cs create mode 100644 src/NzbDrone.Core/Indexers/Exceptions/IndexerException.cs rename src/NzbDrone.Core/Indexers/{Newznab => Exceptions}/SizeParsingException.cs (83%) delete mode 100644 src/NzbDrone.Core/Indexers/Fanzub/FanzubParser.cs create mode 100644 src/NzbDrone.Core/Indexers/Fanzub/FanzubRequestGenerator.cs create mode 100644 src/NzbDrone.Core/Indexers/HttpIndexerBase.cs create mode 100644 src/NzbDrone.Core/Indexers/IIndexerRequestGenerator.cs delete mode 100644 src/NzbDrone.Core/Indexers/IParseFeed.cs create mode 100644 src/NzbDrone.Core/Indexers/IProcessIndexerResponse.cs delete mode 100644 src/NzbDrone.Core/Indexers/IndexerFetchService.cs create mode 100644 src/NzbDrone.Core/Indexers/IndexerRequest.cs delete mode 100644 src/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs delete mode 100644 src/NzbDrone.Core/Indexers/Newznab/NewznabPreProcessor.cs create mode 100644 src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs create mode 100644 src/NzbDrone.Core/Indexers/Newznab/NewznabRssParser.cs delete mode 100644 src/NzbDrone.Core/Indexers/NewznabTestService.cs create mode 100644 src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs rename src/NzbDrone.Core/Indexers/Omgwtfnzbs/{OmgwtfnzbsParser.cs => OmgwtfnzbsRssParser.cs} (60%) create mode 100644 src/NzbDrone.Core/Indexers/RssIndexerRequestGenerator.cs create mode 100644 src/NzbDrone.Core/Indexers/RssParser.cs delete mode 100644 src/NzbDrone.Core/Indexers/RssParserBase.cs rename src/NzbDrone.Core/Indexers/Wombles/{WomblesParser.cs => WomblesRssParser.cs} (72%) diff --git a/src/NzbDrone.Common/Http/HttpAccept.cs b/src/NzbDrone.Common/Http/HttpAccept.cs new file mode 100644 index 000000000..daf94449d --- /dev/null +++ b/src/NzbDrone.Common/Http/HttpAccept.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Common.Http +{ + public sealed class HttpAccept + { + public static readonly HttpAccept Rss = new HttpAccept("application/rss+xml, text/rss+xml, text/xml"); + public static readonly HttpAccept Json = new HttpAccept("application/json"); + public static readonly HttpAccept Html = new HttpAccept("text/html"); + + public String Value { get; private set; } + + public HttpAccept(String accept) + { + Value = accept; + } + + public override string ToString() + { + return Value; + } + } +} diff --git a/src/NzbDrone.Common/Http/HttpClient.cs b/src/NzbDrone.Common/Http/HttpClient.cs index 4da22ba66..5c864d7f6 100644 --- a/src/NzbDrone.Common/Http/HttpClient.cs +++ b/src/NzbDrone.Common/Http/HttpClient.cs @@ -46,6 +46,7 @@ public HttpResponse Execute(HttpRequest request) webRequest.Method = request.Method.ToString(); webRequest.UserAgent = _userAgent; webRequest.KeepAlive = false; + webRequest.AllowAutoRedirect = request.AllowAutoRedirect; if (!RuntimeInfoBase.IsProduction) { @@ -61,8 +62,7 @@ public HttpResponse Execute(HttpRequest request) if (!request.Body.IsNullOrWhiteSpace()) { - var bytes = new byte[request.Body.Length * sizeof(char)]; - Buffer.BlockCopy(request.Body.ToCharArray(), 0, bytes, 0, bytes.Length); + var bytes = request.Headers.GetEncodingFromContentType().GetBytes(request.Body.ToCharArray()); webRequest.ContentLength = bytes.Length; using (var writeStream = webRequest.GetRequestStream()) diff --git a/src/NzbDrone.Common/Http/HttpException.cs b/src/NzbDrone.Common/Http/HttpException.cs index 3c52fc111..3c8e38249 100644 --- a/src/NzbDrone.Common/Http/HttpException.cs +++ b/src/NzbDrone.Common/Http/HttpException.cs @@ -14,6 +14,12 @@ public HttpException(HttpRequest request, HttpResponse response) Response = response; } + public HttpException(HttpResponse response) + : this(response.Request, response) + { + + } + public override string ToString() { if (Response != null) diff --git a/src/NzbDrone.Common/Http/HttpHeader.cs b/src/NzbDrone.Common/Http/HttpHeader.cs index b7bb3c617..16033e020 100644 --- a/src/NzbDrone.Common/Http/HttpHeader.cs +++ b/src/NzbDrone.Common/Http/HttpHeader.cs @@ -1,6 +1,8 @@ using System; +using System.Linq; using System.Collections.Generic; using System.Collections.Specialized; +using System.Text; namespace NzbDrone.Common.Http { @@ -66,5 +68,35 @@ public string Accept this["Accept"] = value; } } + + public Encoding GetEncodingFromContentType() + { + Encoding encoding = null; + + if (ContentType.IsNotNullOrWhiteSpace()) + { + var charset = ContentType.ToLowerInvariant() + .Split(';', '=', ' ') + .SkipWhile(v => v != "charset") + .Skip(1).FirstOrDefault(); + + if (charset.IsNotNullOrWhiteSpace()) + { + encoding = Encoding.GetEncoding(charset); + } + } + + if (encoding == null) + { + // TODO: Find encoding by Byte order mask. + } + + if (encoding == null) + { + encoding = Encoding.UTF8; + } + + return encoding; + } } } \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/HttpRequest.cs b/src/NzbDrone.Common/Http/HttpRequest.cs index 6fba92e10..34c2954d6 100644 --- a/src/NzbDrone.Common/Http/HttpRequest.cs +++ b/src/NzbDrone.Common/Http/HttpRequest.cs @@ -6,14 +6,19 @@ namespace NzbDrone.Common.Http { public class HttpRequest { - private readonly Dictionary _segments; - public HttpRequest(string url) + public HttpRequest(string url, HttpAccept httpAccept = null) { UriBuilder = new UriBuilder(url); Headers = new HttpHeader(); _segments = new Dictionary(); + AllowAutoRedirect = true; + + if (httpAccept != null) + { + Headers.Accept = httpAccept.Value; + } } public UriBuilder UriBuilder { get; private set; } @@ -38,6 +43,7 @@ public Uri Url public string Body { get; set; } public NetworkCredential NetworkCredential { get; set; } public bool SuppressHttpError { get; set; } + public bool AllowAutoRedirect { get; set; } public override string ToString() { diff --git a/src/NzbDrone.Common/Http/HttpResponse.cs b/src/NzbDrone.Common/Http/HttpResponse.cs index f031b4080..999d0a4ad 100644 --- a/src/NzbDrone.Common/Http/HttpResponse.cs +++ b/src/NzbDrone.Common/Http/HttpResponse.cs @@ -21,7 +21,7 @@ public HttpResponse(HttpRequest request, HttpHeader headers, String content, Htt { Request = request; Headers = headers; - ResponseData = Encoding.UTF8.GetBytes(content); + ResponseData = Headers.GetEncodingFromContentType().GetBytes(content); _content = content; StatusCode = statusCode; } @@ -39,7 +39,7 @@ public String Content { if (_content == null) { - _content = GetStringFromResponseData(); + _content = Headers.GetEncodingFromContentType().GetString(ResponseData); } return _content; @@ -66,36 +66,6 @@ public override string ToString() return result; } - - protected virtual String GetStringFromResponseData() - { - Encoding encoding = null; - - if (Headers.ContentType.IsNotNullOrWhiteSpace()) - { - var charset = Headers.ContentType.ToLowerInvariant() - .Split(';', '=', ' ') - .SkipWhile(v => v != "charset") - .Skip(1).FirstOrDefault(); - - if (charset.IsNotNullOrWhiteSpace()) - { - encoding = Encoding.GetEncoding(charset); - } - } - - if (encoding == null) - { - // TODO: Find encoding by Byte order mask. - } - - if (encoding == null) - { - encoding = Encoding.UTF8; - } - - return encoding.GetString(ResponseData); - } } diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 30dbe2478..c1a341ba5 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -114,6 +114,7 @@ Component + diff --git a/src/NzbDrone.Core.Test/Files/RSS/fanzub.xml b/src/NzbDrone.Core.Test/Files/RSS/fanzub.xml new file mode 100644 index 000000000..bd4175086 --- /dev/null +++ b/src/NzbDrone.Core.Test/Files/RSS/fanzub.xml @@ -0,0 +1,37 @@ + + + + Anime :: Fanzub + http://www.fanzub.com/ + A Usenet Search Engine for Japanese Media + en-us + + + [Vivid] Hanayamata - 10 [A33D6606] + http://fanzub.com/nzb/296464 + <i>Age</i>: 0 days<br /><i>Size</i>: 530.48 MiB<br /><i>Parts</i>: 100%<br /><i>Files</i>: 1 other, 8 par2<br /><i>Subject</i>: [9/9] [Vivid] Hanayamata - 10 [A33D6606].vol63+27.par2 (1/28) + Anime + Sat, 13 Sep 2014 12:56:53 +0000 + + http://fanzub.com/nzb/296464 + + + (Sniper2000) - Pokemon HD - XY 37 + http://fanzub.com/nzb/296456 + <i>Age</i>: 0 days<br /><i>Size</i>: 2.79 GiB<br /><i>Parts</i>: 100%<br /><i>Files</i>: 1 nzb, 1 other, 77 par2, 30 rar<br /><i>Subject</i>: (Sniper2000) [108/108] - "XY 37.vol183+176.PAR2"Pokemon HD (1/272) + Anime + Sat, 13 Sep 2014 12:38:03 +0000 + + http://fanzub.com/nzb/296456 + + + [HorribleSubs] Kindaichi Case Files R - 23 [480p].mkv + http://fanzub.com/nzb/296472 + <i>Age</i>: 0 days<br /><i>Size</i>: 153.87 MiB<br /><i>Parts</i>: 100%<br /><i>Files</i>: 7 par2, 6 split<br /><i>Subject</i>: [HorribleSubs] Kindaichi Case Files R - 23 [480p] [13/13] - "[HorribleSubs] Kindaichi Case Files R - 23 [480p].mkv.vol31+06.par2" yEnc (1/7) + Anime + Sat, 13 Sep 2014 11:51:59 +0000 + + http://fanzub.com/nzb/296472 + + + diff --git a/src/NzbDrone.Core.Test/Files/RSS/newznab.xml b/src/NzbDrone.Core.Test/Files/RSS/newznab_nzb_su.xml similarity index 82% rename from src/NzbDrone.Core.Test/Files/RSS/newznab.xml rename to src/NzbDrone.Core.Test/Files/RSS/newznab_nzb_su.xml index c1509403a..ea68ee154 100644 --- a/src/NzbDrone.Core.Test/Files/RSS/newznab.xml +++ b/src/NzbDrone.Core.Test/Files/RSS/newznab_nzb_su.xml @@ -1,7 +1,7 @@ - + Nzb.su Nzb.su Feed http://nzb.su/ @@ -22,13 +22,13 @@ White.Collar.S03E05.720p.HDTV.X264-DIMENSION http://nzb.su/details/24967ef4c2e26296c65d3bbfa97aa8fe - http://nzb.su/getnzb/24967ef4c2e26296c65d3bbfa97aa8fe.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/24967ef4c2e26296c65d3bbfa97aa8fe.nzb&i=37292&r=xxx http://nzb.su/details/24967ef4c2e26296c65d3bbfa97aa8fe#comments Mon, 27 Feb 2012 11:09:39 -0500 TV > HD White.Collar.S03E05.720p.HDTV.X264-DIMENSION - + @@ -41,13 +41,13 @@ White.Collar.S03E04.720p.HDTV.X264-DIMENSION http://nzb.su/details/fab3bed2f4169522c3cb2ef24a6e8a5f - http://nzb.su/getnzb/fab3bed2f4169522c3cb2ef24a6e8a5f.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/fab3bed2f4169522c3cb2ef24a6e8a5f.nzb&i=37292&r=xxx http://nzb.su/details/fab3bed2f4169522c3cb2ef24a6e8a5f#comments Mon, 27 Feb 2012 11:14:16 -0500 TV > HD White.Collar.S03E04.720p.HDTV.X264-DIMENSION - + @@ -60,13 +60,13 @@ White.Collar.S03E03.720p.HDTV.x264-CTU http://nzb.su/details/ba12896db486b455706ef5f353a78e81 - http://nzb.su/getnzb/ba12896db486b455706ef5f353a78e81.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/ba12896db486b455706ef5f353a78e81.nzb&i=37292&r=xxx http://nzb.su/details/ba12896db486b455706ef5f353a78e81#comments Mon, 27 Feb 2012 11:14:16 -0500 TV > HD White.Collar.S03E03.720p.HDTV.x264-CTU - + @@ -79,13 +79,13 @@ White.Collar.S03E02.720p.HDTV.X264-DIMENSION http://nzb.su/details/79eacdb15c967465bf6667c46bcff3e4 - http://nzb.su/getnzb/79eacdb15c967465bf6667c46bcff3e4.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/79eacdb15c967465bf6667c46bcff3e4.nzb&i=37292&r=xxx http://nzb.su/details/79eacdb15c967465bf6667c46bcff3e4#comments Mon, 27 Feb 2012 11:12:43 -0500 TV > HD White.Collar.S03E02.720p.HDTV.X264-DIMENSION - + @@ -98,13 +98,13 @@ White.Collar.S03E07.720p.HDTV.x264-IMMERSE http://nzb.su/details/923a97da875283a74127762c061830e1 - http://nzb.su/getnzb/923a97da875283a74127762c061830e1.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/923a97da875283a74127762c061830e1.nzb&i=37292&r=xxx http://nzb.su/details/923a97da875283a74127762c061830e1#comments Mon, 27 Feb 2012 11:11:13 -0500 TV > HD White.Collar.S03E07.720p.HDTV.x264-IMMERSE - + @@ -117,13 +117,13 @@ White.Collar.S02E14.720p.HDTV.X264-DIMENSION http://nzb.su/details/320fe82676c117e1e9c595a4d4cce8ff - http://nzb.su/getnzb/320fe82676c117e1e9c595a4d4cce8ff.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/320fe82676c117e1e9c595a4d4cce8ff.nzb&i=37292&r=xxx http://nzb.su/details/320fe82676c117e1e9c595a4d4cce8ff#comments Mon, 27 Feb 2012 11:09:39 -0500 TV > HD White.Collar.S02E14.720p.HDTV.X264-DIMENSION - + @@ -136,13 +136,13 @@ Head Rush 2010-09-17 Human Conductions 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/07af6cf4563e3e8c76feb52401d954e2 - http://nzb.su/getnzb/07af6cf4563e3e8c76feb52401d954e2.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/07af6cf4563e3e8c76feb52401d954e2.nzb&i=37292&r=xxx http://nzb.su/details/07af6cf4563e3e8c76feb52401d954e2#comments Mon, 27 Feb 2012 10:59:04 -0500 TV > HD Head Rush 2010-09-17 Human Conductions 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -155,13 +155,13 @@ Fringe S04E13 720p WMVHD NeoDweezil http://nzb.su/details/3c1e005678df784ae0062ac47e7b4245 - http://nzb.su/getnzb/3c1e005678df784ae0062ac47e7b4245.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/3c1e005678df784ae0062ac47e7b4245.nzb&i=37292&r=xxx http://nzb.su/details/3c1e005678df784ae0062ac47e7b4245#comments Mon, 27 Feb 2012 10:52:11 -0500 TV > HD Fringe S04E13 720p WMVHD NeoDweezil - + @@ -174,13 +174,13 @@ The.Indian.Doctor.S02E01.HDTV.x264-TLA http://nzb.su/details/3fc0305f87d841eb20a89fac1f8fc17a - http://nzb.su/getnzb/3fc0305f87d841eb20a89fac1f8fc17a.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/3fc0305f87d841eb20a89fac1f8fc17a.nzb&i=37292&r=xxx http://nzb.su/details/3fc0305f87d841eb20a89fac1f8fc17a#comments Mon, 27 Feb 2012 10:39:19 -0500 TV > SD The.Indian.Doctor.S02E01.HDTV.x264-TLA - + @@ -193,13 +193,13 @@ Giada at Home GH0412H Pure Comfort 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/9cfb651bc08b687be3c4e7bb865a78ec - http://nzb.su/getnzb/9cfb651bc08b687be3c4e7bb865a78ec.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/9cfb651bc08b687be3c4e7bb865a78ec.nzb&i=37292&r=xxx http://nzb.su/details/9cfb651bc08b687be3c4e7bb865a78ec#comments Mon, 27 Feb 2012 10:39:18 -0500 TV > HD Giada at Home GH0412H Pure Comfort 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -212,13 +212,13 @@ Black Forest (2012) 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/f357688542be93d7c258557f9e1d2d52 - http://nzb.su/getnzb/f357688542be93d7c258557f9e1d2d52.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/f357688542be93d7c258557f9e1d2d52.nzb&i=37292&r=xxx http://nzb.su/details/f357688542be93d7c258557f9e1d2d52#comments Mon, 27 Feb 2012 10:33:46 -0500 TV > HD Black Forest (2012) 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -231,13 +231,13 @@ The.Indian.Doctor.S02E01.720p.HDTV.x264-TLA http://nzb.su/details/8df60f1ac194cab27859d21b958704a9 - http://nzb.su/getnzb/8df60f1ac194cab27859d21b958704a9.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/8df60f1ac194cab27859d21b958704a9.nzb&i=37292&r=xxx http://nzb.su/details/8df60f1ac194cab27859d21b958704a9#comments Mon, 27 Feb 2012 10:21:23 -0500 TV > HD The.Indian.Doctor.S02E01.720p.HDTV.x264-TLA - + @@ -250,13 +250,13 @@ American Weed S01E01 Marijuana Drama 720p HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/28283f97bc847e21e19ebefecb7c20ca - http://nzb.su/getnzb/28283f97bc847e21e19ebefecb7c20ca.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/28283f97bc847e21e19ebefecb7c20ca.nzb&i=37292&r=xxx http://nzb.su/details/28283f97bc847e21e19ebefecb7c20ca#comments Mon, 27 Feb 2012 10:11:12 -0500 TV > HD American Weed S01E01 Marijuana Drama 720p HDTV DD5.1 MPEG2-TrollHD - + @@ -269,13 +269,13 @@ Space.1999.S01E10.1080p.BluRay.x264-aAF http://nzb.su/details/e34e9d1d77795786d93b8b3b01cd53b7 - http://nzb.su/getnzb/e34e9d1d77795786d93b8b3b01cd53b7.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/e34e9d1d77795786d93b8b3b01cd53b7.nzb&i=37292&r=xxx http://nzb.su/details/e34e9d1d77795786d93b8b3b01cd53b7#comments Mon, 27 Feb 2012 09:31:29 -0500 TV > HD Space.1999.S01E10.1080p.BluRay.x264-aAF - + @@ -288,13 +288,13 @@ Space.1999.S01E09.1080p.BluRay.x264-aAF http://nzb.su/details/0c3fea48354250895a2c2f218331d9c8 - http://nzb.su/getnzb/0c3fea48354250895a2c2f218331d9c8.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/0c3fea48354250895a2c2f218331d9c8.nzb&i=37292&r=xxx http://nzb.su/details/0c3fea48354250895a2c2f218331d9c8#comments Mon, 27 Feb 2012 09:28:46 -0500 TV > HD Space.1999.S01E09.1080p.BluRay.x264-aAF - + @@ -307,13 +307,13 @@ Space.1999.S01E08.1080p.BluRay.x264-aAF http://nzb.su/details/a17fad5ce97f75d13af98a956511c84e - http://nzb.su/getnzb/a17fad5ce97f75d13af98a956511c84e.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/a17fad5ce97f75d13af98a956511c84e.nzb&i=37292&r=xxx http://nzb.su/details/a17fad5ce97f75d13af98a956511c84e#comments Mon, 27 Feb 2012 09:25:55 -0500 TV > HD Space.1999.S01E08.1080p.BluRay.x264-aAF - + @@ -326,13 +326,13 @@ Space.1999.S01E07.1080p.BluRay.x264-aAF http://nzb.su/details/f64e6740e11f03d3d793f6ec52b64ff9 - http://nzb.su/getnzb/f64e6740e11f03d3d793f6ec52b64ff9.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/f64e6740e11f03d3d793f6ec52b64ff9.nzb&i=37292&r=xxx http://nzb.su/details/f64e6740e11f03d3d793f6ec52b64ff9#comments Mon, 27 Feb 2012 09:21:36 -0500 TV > HD Space.1999.S01E07.1080p.BluRay.x264-aAF - + @@ -345,13 +345,13 @@ Space.1999.S01E06.1080p.BluRay.x264-aAF http://nzb.su/details/b6aa66e8139b083073f0ca172f1998d0 - http://nzb.su/getnzb/b6aa66e8139b083073f0ca172f1998d0.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/b6aa66e8139b083073f0ca172f1998d0.nzb&i=37292&r=xxx http://nzb.su/details/b6aa66e8139b083073f0ca172f1998d0#comments Mon, 27 Feb 2012 09:16:57 -0500 TV > HD Space.1999.S01E06.1080p.BluRay.x264-aAF - + @@ -364,13 +364,13 @@ Space.1999.S01E05.1080p.BluRay.x264-aAF http://nzb.su/details/12998f81119de5de3c3b27f345bfae39 - http://nzb.su/getnzb/12998f81119de5de3c3b27f345bfae39.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/12998f81119de5de3c3b27f345bfae39.nzb&i=37292&r=xxx http://nzb.su/details/12998f81119de5de3c3b27f345bfae39#comments Mon, 27 Feb 2012 09:11:46 -0500 TV > HD Space.1999.S01E05.1080p.BluRay.x264-aAF - + @@ -383,13 +383,13 @@ My.Kitchen.Rules.AU.S03E17.PDTV.XviD.BF1 http://nzb.su/details/453f52cd16c2b2007a9a0e8fabc61d84 - http://nzb.su/getnzb/453f52cd16c2b2007a9a0e8fabc61d84.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/453f52cd16c2b2007a9a0e8fabc61d84.nzb&i=37292&r=xxx http://nzb.su/details/453f52cd16c2b2007a9a0e8fabc61d84#comments Mon, 27 Feb 2012 09:24:25 -0500 TV > SD My.Kitchen.Rules.AU.S03E17.PDTV.XviD.BF1 - + @@ -402,13 +402,13 @@ Space.1999.S01E04.1080p.BluRay.x264-aAF http://nzb.su/details/35925829150dd7f213a3dae2b185a6d1 - http://nzb.su/getnzb/35925829150dd7f213a3dae2b185a6d1.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/35925829150dd7f213a3dae2b185a6d1.nzb&i=37292&r=xxx http://nzb.su/details/35925829150dd7f213a3dae2b185a6d1#comments Mon, 27 Feb 2012 09:08:45 -0500 TV > HD Space.1999.S01E04.1080p.BluRay.x264-aAF - + @@ -421,13 +421,13 @@ Space.1999.S01E03.1080p.BluRay.x264-aAF http://nzb.su/details/ac1687c426a101f236efc30af613aff1 - http://nzb.su/getnzb/ac1687c426a101f236efc30af613aff1.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/ac1687c426a101f236efc30af613aff1.nzb&i=37292&r=xxx http://nzb.su/details/ac1687c426a101f236efc30af613aff1#comments Mon, 27 Feb 2012 09:02:49 -0500 TV > HD Space.1999.S01E03.1080p.BluRay.x264-aAF - + @@ -440,13 +440,13 @@ Space.1999.S01E02.1080p.BluRay.x264-aAF http://nzb.su/details/717b6d4423970502927f1cc8fe2a6a3b - http://nzb.su/getnzb/717b6d4423970502927f1cc8fe2a6a3b.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/717b6d4423970502927f1cc8fe2a6a3b.nzb&i=37292&r=xxx http://nzb.su/details/717b6d4423970502927f1cc8fe2a6a3b#comments Mon, 27 Feb 2012 08:58:21 -0500 TV > HD Space.1999.S01E02.1080p.BluRay.x264-aAF - + @@ -459,13 +459,13 @@ Space.1999.S01E01.1080p.BluRay.x264-aAF http://nzb.su/details/14e7dc51a27d16f523d4b43c3d976eea - http://nzb.su/getnzb/14e7dc51a27d16f523d4b43c3d976eea.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/14e7dc51a27d16f523d4b43c3d976eea.nzb&i=37292&r=xxx http://nzb.su/details/14e7dc51a27d16f523d4b43c3d976eea#comments Mon, 27 Feb 2012 08:56:54 -0500 TV > HD Space.1999.S01E01.1080p.BluRay.x264-aAF - + @@ -478,13 +478,13 @@ National.Geographic.Forbidden.Tomb.of.Genghis.Khan.720p.HDTV.x264-GeT http://nzb.su/details/bd7e1fc46db570ac2e21733f34e44573 - http://nzb.su/getnzb/bd7e1fc46db570ac2e21733f34e44573.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/bd7e1fc46db570ac2e21733f34e44573.nzb&i=37292&r=xxx http://nzb.su/details/bd7e1fc46db570ac2e21733f34e44573#comments Mon, 27 Feb 2012 08:18:31 -0500 TV > HD National.Geographic.Forbidden.Tomb.of.Genghis.Khan.720p.HDTV.x264-GeT - + @@ -497,13 +497,13 @@ Chicago's Best - Western Suburbs 2 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/6e948865e3dd4af740a68f944e8afdd3 - http://nzb.su/getnzb/6e948865e3dd4af740a68f944e8afdd3.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/6e948865e3dd4af740a68f944e8afdd3.nzb&i=37292&r=xxx http://nzb.su/details/6e948865e3dd4af740a68f944e8afdd3#comments Mon, 27 Feb 2012 08:02:17 -0500 TV > HD Chicago's Best - Western Suburbs 2 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -516,13 +516,13 @@ Star.Wars.Episode.VI.Return.Of.The.Jedi.1983.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD http://nzb.su/details/d4c0b3b28421fbe9e14ea6683889b125 - http://nzb.su/getnzb/d4c0b3b28421fbe9e14ea6683889b125.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/d4c0b3b28421fbe9e14ea6683889b125.nzb&i=37292&r=xxx http://nzb.su/details/d4c0b3b28421fbe9e14ea6683889b125#comments Mon, 27 Feb 2012 07:36:39 -0500 TV > HD Star.Wars.Episode.VI.Return.Of.The.Jedi.1983.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD - + @@ -535,13 +535,13 @@ Bondi.Rescue.S07E04.WS.PDTV.XviD-RTA http://nzb.su/details/20b6cba13475fa349166a999ad924440 - http://nzb.su/getnzb/20b6cba13475fa349166a999ad924440.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/20b6cba13475fa349166a999ad924440.nzb&i=37292&r=xxx http://nzb.su/details/20b6cba13475fa349166a999ad924440#comments Mon, 27 Feb 2012 07:26:02 -0500 TV > SD Bondi.Rescue.S07E04.WS.PDTV.XviD-RTA - + @@ -554,13 +554,13 @@ Star.Wars.Episode.I.The.Phantom.Menace.1999.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD http://nzb.su/details/22e31ed44ff26b110f8a910beb7d6444 - http://nzb.su/getnzb/22e31ed44ff26b110f8a910beb7d6444.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/22e31ed44ff26b110f8a910beb7d6444.nzb&i=37292&r=xxx http://nzb.su/details/22e31ed44ff26b110f8a910beb7d6444#comments Mon, 27 Feb 2012 07:13:34 -0500 TV > HD Star.Wars.Episode.I.The.Phantom.Menace.1999.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD - + @@ -573,13 +573,13 @@ The.Biggest.Loser.Australia.s07e23.PDTV.XviD.BF1 http://nzb.su/details/c937520cb1d94bd17bc5378a0450b1a2 - http://nzb.su/getnzb/c937520cb1d94bd17bc5378a0450b1a2.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/c937520cb1d94bd17bc5378a0450b1a2.nzb&i=37292&r=xxx http://nzb.su/details/c937520cb1d94bd17bc5378a0450b1a2#comments Mon, 27 Feb 2012 07:26:02 -0500 TV > SD The.Biggest.Loser.Australia.s07e23.PDTV.XviD.BF1 - + @@ -592,13 +592,13 @@ Star.Wars.Episode.II.Attack.Of.The.Clones.2002.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD http://nzb.su/details/a8401920efbd36e1ed518f629f06958a - http://nzb.su/getnzb/a8401920efbd36e1ed518f629f06958a.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/a8401920efbd36e1ed518f629f06958a.nzb&i=37292&r=xxx http://nzb.su/details/a8401920efbd36e1ed518f629f06958a#comments Mon, 27 Feb 2012 06:46:20 -0500 TV > HD Star.Wars.Episode.II.Attack.Of.The.Clones.2002.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD - + @@ -611,13 +611,13 @@ The River - S01E03 - Los Ciegos - 264x720p http://nzb.su/details/513d74cea591a23e5d9e56336117052f - http://nzb.su/getnzb/513d74cea591a23e5d9e56336117052f.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/513d74cea591a23e5d9e56336117052f.nzb&i=37292&r=xxx http://nzb.su/details/513d74cea591a23e5d9e56336117052f#comments Mon, 27 Feb 2012 10:48:45 -0500 TV > HD The River - S01E03 - Los Ciegos - 264x720p - + @@ -630,13 +630,13 @@ My.Kitchen.Rules.S03E17.WS.PDTV.x264-TASTETV http://nzb.su/details/00ef9f029b8bda6be9e095f09ce26e60 - http://nzb.su/getnzb/00ef9f029b8bda6be9e095f09ce26e60.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/00ef9f029b8bda6be9e095f09ce26e60.nzb&i=37292&r=xxx http://nzb.su/details/00ef9f029b8bda6be9e095f09ce26e60#comments Mon, 27 Feb 2012 06:27:16 -0500 TV > SD My.Kitchen.Rules.S03E17.WS.PDTV.x264-TASTETV - + @@ -649,13 +649,13 @@ Catch 21 2011-05-23 1080i HDTV DD2.0 MPEG2-TrollHD http://nzb.su/details/b8f1a51e098b81010d5f493a3e02dc95 - http://nzb.su/getnzb/b8f1a51e098b81010d5f493a3e02dc95.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/b8f1a51e098b81010d5f493a3e02dc95.nzb&i=37292&r=xxx http://nzb.su/details/b8f1a51e098b81010d5f493a3e02dc95#comments Mon, 27 Feb 2012 06:37:02 -0500 TV > HD Catch 21 2011-05-23 1080i HDTV DD2.0 MPEG2-TrollHD - + @@ -668,13 +668,13 @@ Star.Wars.Episode.III.Revenge.Of.The.Sith.2005.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD http://nzb.su/details/04784d8f333181048060c2ad61d3e580 - http://nzb.su/getnzb/04784d8f333181048060c2ad61d3e580.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/04784d8f333181048060c2ad61d3e580.nzb&i=37292&r=xxx http://nzb.su/details/04784d8f333181048060c2ad61d3e580#comments Mon, 27 Feb 2012 05:59:02 -0500 TV > HD Star.Wars.Episode.III.Revenge.Of.The.Sith.2005.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD - + @@ -687,13 +687,13 @@ Unwrapped CW1612H Easy as Pie 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/423632bf02ae6c0b1c3da3eccf79bb44 - http://nzb.su/getnzb/423632bf02ae6c0b1c3da3eccf79bb44.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/423632bf02ae6c0b1c3da3eccf79bb44.nzb&i=37292&r=xxx http://nzb.su/details/423632bf02ae6c0b1c3da3eccf79bb44#comments Mon, 27 Feb 2012 05:57:28 -0500 TV > HD Unwrapped CW1612H Easy as Pie 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -706,13 +706,13 @@ Unwrapped CW1312H Sack Lunch 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/77e1ffbc0c390300628e96b02f84d379 - http://nzb.su/getnzb/77e1ffbc0c390300628e96b02f84d379.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/77e1ffbc0c390300628e96b02f84d379.nzb&i=37292&r=xxx http://nzb.su/details/77e1ffbc0c390300628e96b02f84d379#comments Mon, 27 Feb 2012 05:52:15 -0500 TV > HD Unwrapped CW1312H Sack Lunch 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -725,13 +725,13 @@ Unforgettable S01E16 Heartbreak 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/f065d1f75d19e8e7a9073bca8c90d544 - http://nzb.su/getnzb/f065d1f75d19e8e7a9073bca8c90d544.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/f065d1f75d19e8e7a9073bca8c90d544.nzb&i=37292&r=xxx http://nzb.su/details/f065d1f75d19e8e7a9073bca8c90d544#comments Mon, 27 Feb 2012 05:42:45 -0500 TV > HD Unforgettable S01E16 Heartbreak 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -744,13 +744,13 @@ This Old House S33E20 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/957b62f7489aa4bf6908c69cbd1c9898 - http://nzb.su/getnzb/957b62f7489aa4bf6908c69cbd1c9898.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/957b62f7489aa4bf6908c69cbd1c9898.nzb&i=37292&r=xxx http://nzb.su/details/957b62f7489aa4bf6908c69cbd1c9898#comments Mon, 27 Feb 2012 05:31:06 -0500 TV > HD This Old House S33E20 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -763,13 +763,13 @@ Grimm S01E12 Last Grimm Standing 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/71f97967a20a74683bc15c2cb77ce699 - http://nzb.su/getnzb/71f97967a20a74683bc15c2cb77ce699.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/71f97967a20a74683bc15c2cb77ce699.nzb&i=37292&r=xxx http://nzb.su/details/71f97967a20a74683bc15c2cb77ce699#comments Mon, 27 Feb 2012 05:24:06 -0500 TV > HD Grimm S01E12 Last Grimm Standing 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -782,13 +782,13 @@ Catch 21 2011-05-20 1080i HDTV DD2.0 MPEG2-TrollHD http://nzb.su/details/bf0d691fd49c54b5f93c37bb4f4cd867 - http://nzb.su/getnzb/bf0d691fd49c54b5f93c37bb4f4cd867.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/bf0d691fd49c54b5f93c37bb4f4cd867.nzb&i=37292&r=xxx http://nzb.su/details/bf0d691fd49c54b5f93c37bb4f4cd867#comments Mon, 27 Feb 2012 05:32:39 -0500 TV > HD Catch 21 2011-05-20 1080i HDTV DD2.0 MPEG2-TrollHD - + @@ -801,13 +801,13 @@ Luck.S01E05.PROPER.720p.HDTV.x264-2HD http://nzb.su/details/f47617ed790b37ffa8cfc8be4d10f1df - http://nzb.su/getnzb/f47617ed790b37ffa8cfc8be4d10f1df.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/f47617ed790b37ffa8cfc8be4d10f1df.nzb&i=37292&r=xxx http://nzb.su/details/f47617ed790b37ffa8cfc8be4d10f1df#comments Mon, 27 Feb 2012 05:10:07 -0500 TV > HD Luck.S01E05.PROPER.720p.HDTV.x264-2HD - + @@ -820,13 +820,13 @@ Ask This Old House S10E20 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/82167e476238b16cc1f55e942a2c0434 - http://nzb.su/getnzb/82167e476238b16cc1f55e942a2c0434.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/82167e476238b16cc1f55e942a2c0434.nzb&i=37292&r=xxx http://nzb.su/details/82167e476238b16cc1f55e942a2c0434#comments Mon, 27 Feb 2012 05:13:13 -0500 TV > HD Ask This Old House S10E20 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -839,13 +839,13 @@ According to Jim S07E04 The Perfect Fight 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/d8e942fde13b65b97b9e9ad34f6c4e56 - http://nzb.su/getnzb/d8e942fde13b65b97b9e9ad34f6c4e56.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/d8e942fde13b65b97b9e9ad34f6c4e56.nzb&i=37292&r=xxx http://nzb.su/details/d8e942fde13b65b97b9e9ad34f6c4e56#comments Mon, 27 Feb 2012 05:07:24 -0500 TV > HD According to Jim S07E04 The Perfect Fight 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -858,13 +858,13 @@ According to Jim S07E03 Safety Last 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/976cc03bb2519e27113bedafd6037619 - http://nzb.su/getnzb/976cc03bb2519e27113bedafd6037619.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/976cc03bb2519e27113bedafd6037619.nzb&i=37292&r=xxx http://nzb.su/details/976cc03bb2519e27113bedafd6037619#comments Mon, 27 Feb 2012 05:00:28 -0500 TV > HD According to Jim S07E03 Safety Last 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -877,13 +877,13 @@ The.84th.Annual.Academy.Awards.2012.HDTV.XviD-2HD http://nzb.su/details/4e6acc683c21aa3c8f4ea8e3aa9ab3d4 - http://nzb.su/getnzb/4e6acc683c21aa3c8f4ea8e3aa9ab3d4.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/4e6acc683c21aa3c8f4ea8e3aa9ab3d4.nzb&i=37292&r=xxx http://nzb.su/details/4e6acc683c21aa3c8f4ea8e3aa9ab3d4#comments Mon, 27 Feb 2012 04:48:41 -0500 TV > SD The.84th.Annual.Academy.Awards.2012.HDTV.XviD-2HD - + @@ -896,13 +896,13 @@ A Gifted Man S01E15 In Case of Letting Go 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/148268f41d539093ca2d78ffd8682a3e - http://nzb.su/getnzb/148268f41d539093ca2d78ffd8682a3e.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/148268f41d539093ca2d78ffd8682a3e.nzb&i=37292&r=xxx http://nzb.su/details/148268f41d539093ca2d78ffd8682a3e#comments Mon, 27 Feb 2012 04:55:13 -0500 TV > HD A Gifted Man S01E15 In Case of Letting Go 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -915,13 +915,13 @@ Star.Wars.Episode.IV.A.New.Hope.1977.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD http://nzb.su/details/7f52cc22f0b8e8db4d7fde708f229887 - http://nzb.su/getnzb/7f52cc22f0b8e8db4d7fde708f229887.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/7f52cc22f0b8e8db4d7fde708f229887.nzb&i=37292&r=xxx http://nzb.su/details/7f52cc22f0b8e8db4d7fde708f229887#comments Mon, 27 Feb 2012 04:34:54 -0500 TV > HD Star.Wars.Episode.IV.A.New.Hope.1977.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD - + @@ -934,13 +934,13 @@ The.84th.Annual.Academy.Awards.2012.720p.HDTV.x264-2HD http://nzb.su/details/8b4859648c0084a19f58d34b1070d705 - http://nzb.su/getnzb/8b4859648c0084a19f58d34b1070d705.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/8b4859648c0084a19f58d34b1070d705.nzb&i=37292&r=xxx http://nzb.su/details/8b4859648c0084a19f58d34b1070d705#comments Mon, 27 Feb 2012 04:28:30 -0500 TV > HD The.84th.Annual.Academy.Awards.2012.720p.HDTV.x264-2HD - + @@ -953,13 +953,13 @@ Full.Metal.Jousting.S01E03.Death.Sticks.and.a.Coffin.720p.HDTV.x264-MOMENTUM http://nzb.su/details/2b90fc4d321c63df54f74219e1cf32c4 - http://nzb.su/getnzb/2b90fc4d321c63df54f74219e1cf32c4.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/2b90fc4d321c63df54f74219e1cf32c4.nzb&i=37292&r=xxx http://nzb.su/details/2b90fc4d321c63df54f74219e1cf32c4#comments Mon, 27 Feb 2012 04:12:00 -0500 TV > HD Full.Metal.Jousting.S01E03.Death.Sticks.and.a.Coffin.720p.HDTV.x264-MOMENTUM - + @@ -972,13 +972,13 @@ Full.Metal.Jousting.S01E03.Death.Sticks.and.a.Coffin.HDTV.x264-MOMENTUM http://nzb.su/details/814b5ca4feb747d6dc975fb4c13495dc - http://nzb.su/getnzb/814b5ca4feb747d6dc975fb4c13495dc.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/814b5ca4feb747d6dc975fb4c13495dc.nzb&i=37292&r=xxx http://nzb.su/details/814b5ca4feb747d6dc975fb4c13495dc#comments Mon, 27 Feb 2012 03:39:56 -0500 TV > SD Full.Metal.Jousting.S01E03.Death.Sticks.and.a.Coffin.HDTV.x264-MOMENTUM - + @@ -991,13 +991,13 @@ Star.Wars.Episode.V.The.Empire.Strikes.Back.1980.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD http://nzb.su/details/94d016064f91a77f3afce41d3cb7fec4 - http://nzb.su/getnzb/94d016064f91a77f3afce41d3cb7fec4.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/94d016064f91a77f3afce41d3cb7fec4.nzb&i=37292&r=xxx http://nzb.su/details/94d016064f91a77f3afce41d3cb7fec4#comments Mon, 27 Feb 2012 03:34:20 -0500 TV > HD Star.Wars.Episode.V.The.Empire.Strikes.Back.1980.DTS-HD.DTS.MULTISUBS.1080p.BluRay.x264.HQ-TUSAHD - + @@ -1010,13 +1010,13 @@ Iron.Chef.America.S10E08.Flay.vs.Hastings.HDTV.x264-MOMENTUM http://nzb.su/details/fecfc46859bf2c451671c1865e685190 - http://nzb.su/getnzb/fecfc46859bf2c451671c1865e685190.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/fecfc46859bf2c451671c1865e685190.nzb&i=37292&r=xxx http://nzb.su/details/fecfc46859bf2c451671c1865e685190#comments Mon, 27 Feb 2012 03:34:22 -0500 TV > SD Iron.Chef.America.S10E08.Flay.vs.Hastings.HDTV.x264-MOMENTUM - + @@ -1029,13 +1029,13 @@ Iron.Chef.America.S10E08.Flay.vs.Hastings.720p.HDTV.x264-MOMENTUM http://nzb.su/details/09ece094563001dcc03765acb1b6616f - http://nzb.su/getnzb/09ece094563001dcc03765acb1b6616f.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/09ece094563001dcc03765acb1b6616f.nzb&i=37292&r=xxx http://nzb.su/details/09ece094563001dcc03765acb1b6616f#comments Mon, 27 Feb 2012 03:34:21 -0500 TV > HD Iron.Chef.America.S10E08.Flay.vs.Hastings.720p.HDTV.x264-MOMENTUM - + @@ -1048,13 +1048,13 @@ Less.Than.Kind.S03E09.720p.HDTV.x264-2HD http://nzb.su/details/8e2a809cb7c8ea130e99995786a96219 - http://nzb.su/getnzb/8e2a809cb7c8ea130e99995786a96219.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/8e2a809cb7c8ea130e99995786a96219.nzb&i=37292&r=xxx http://nzb.su/details/8e2a809cb7c8ea130e99995786a96219#comments Mon, 27 Feb 2012 03:34:22 -0500 TV > HD Less.Than.Kind.S03E09.720p.HDTV.x264-2HD - + @@ -1067,13 +1067,13 @@ Catch 21 2011-05-19 1080i HDTV DD2.0 MPEG2-TrollHD http://nzb.su/details/7d9662a466f327f2dd2a2683f479d657 - http://nzb.su/getnzb/7d9662a466f327f2dd2a2683f479d657.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/7d9662a466f327f2dd2a2683f479d657.nzb&i=37292&r=xxx http://nzb.su/details/7d9662a466f327f2dd2a2683f479d657#comments Mon, 27 Feb 2012 03:34:22 -0500 TV > HD Catch 21 2011-05-19 1080i HDTV DD2.0 MPEG2-TrollHD - + @@ -1086,13 +1086,13 @@ Less.Than.Kind.S03E09.HDTV.XviD-2HD http://nzb.su/details/b8f62815092ed06c7f38dc90e6c5bc43 - http://nzb.su/getnzb/b8f62815092ed06c7f38dc90e6c5bc43.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/b8f62815092ed06c7f38dc90e6c5bc43.nzb&i=37292&r=xxx http://nzb.su/details/b8f62815092ed06c7f38dc90e6c5bc43#comments Mon, 27 Feb 2012 02:28:50 -0500 TV > SD Less.Than.Kind.S03E09.HDTV.XviD-2HD - + @@ -1105,13 +1105,13 @@ Luck.S01E05.HDTV.XviD-2HD http://nzb.su/details/b134b6fb4dcd2c7a73084b43c4febb43 - http://nzb.su/getnzb/b134b6fb4dcd2c7a73084b43c4febb43.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/b134b6fb4dcd2c7a73084b43c4febb43.nzb&i=37292&r=xxx http://nzb.su/details/b134b6fb4dcd2c7a73084b43c4febb43#comments Mon, 27 Feb 2012 02:20:39 -0500 TV > SD Luck.S01E05.HDTV.XviD-2HD - + @@ -1124,13 +1124,13 @@ Jimmy.Kimmel.2012.02.26.After.the.Oscars.Special.HDTV.XviD-2HD http://nzb.su/details/8008065b1ba474100fa3cc7e98acd2a3 - http://nzb.su/getnzb/8008065b1ba474100fa3cc7e98acd2a3.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/8008065b1ba474100fa3cc7e98acd2a3.nzb&i=37292&r=xxx http://nzb.su/details/8008065b1ba474100fa3cc7e98acd2a3#comments Mon, 27 Feb 2012 02:17:58 -0500 TV > SD Jimmy.Kimmel.2012.02.26.After.the.Oscars.Special.HDTV.XviD-2HD - + @@ -1143,13 +1143,13 @@ Spartacus.S02E05.HDTV.XviD-2HD http://nzb.su/details/f3ee7238c75f80524635c3e4197d9035 - http://nzb.su/getnzb/f3ee7238c75f80524635c3e4197d9035.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/f3ee7238c75f80524635c3e4197d9035.nzb&i=37292&r=xxx http://nzb.su/details/f3ee7238c75f80524635c3e4197d9035#comments Mon, 27 Feb 2012 02:10:29 -0500 TV > SD Spartacus.S02E05.HDTV.XviD-2HD - + @@ -1162,13 +1162,13 @@ Jay.Leno.2012.02.22.Tim.Allen.720p.HDTV.x264-BAJSKORV http://nzb.su/details/08a9f199400d3d83c1f6a38379fde982 - http://nzb.su/getnzb/08a9f199400d3d83c1f6a38379fde982.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/08a9f199400d3d83c1f6a38379fde982.nzb&i=37292&r=xxx http://nzb.su/details/08a9f199400d3d83c1f6a38379fde982#comments Mon, 27 Feb 2012 06:00:35 -0500 TV > HD Jay.Leno.2012.02.22.Tim.Allen.720p.HDTV.x264-BAJSKORV - + @@ -1181,13 +1181,13 @@ Jay.Leno.2012.02.09.Denzel.Washington.720p.HDTV.x264-BAJSKORV http://nzb.su/details/52ad1175fe6d34efce14379a84aed88f - http://nzb.su/getnzb/52ad1175fe6d34efce14379a84aed88f.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/52ad1175fe6d34efce14379a84aed88f.nzb&i=37292&r=xxx http://nzb.su/details/52ad1175fe6d34efce14379a84aed88f#comments Mon, 27 Feb 2012 01:59:03 -0500 TV > HD Jay.Leno.2012.02.09.Denzel.Washington.720p.HDTV.x264-BAJSKORV - + @@ -1200,13 +1200,13 @@ Jay.Leno.2012.02.21.Bill.O.Reilly.720p.HDTV.x264-BAJSKORV http://nzb.su/details/5d905e84d39a754f9fb659aa8187c3ab - http://nzb.su/getnzb/5d905e84d39a754f9fb659aa8187c3ab.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/5d905e84d39a754f9fb659aa8187c3ab.nzb&i=37292&r=xxx http://nzb.su/details/5d905e84d39a754f9fb659aa8187c3ab#comments Mon, 27 Feb 2012 01:56:10 -0500 TV > HD Jay.Leno.2012.02.21.Bill.O.Reilly.720p.HDTV.x264-BAJSKORV - + @@ -1219,13 +1219,13 @@ Jay.Leno.2012.02.02.Drew.Barrymore.720p.HDTV.x264-BAJSKORV http://nzb.su/details/527eed252711c8602db523da1f6ed4db - http://nzb.su/getnzb/527eed252711c8602db523da1f6ed4db.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/527eed252711c8602db523da1f6ed4db.nzb&i=37292&r=xxx http://nzb.su/details/527eed252711c8602db523da1f6ed4db#comments Mon, 27 Feb 2012 01:54:07 -0500 TV > HD Jay.Leno.2012.02.02.Drew.Barrymore.720p.HDTV.x264-BAJSKORV - + @@ -1238,13 +1238,13 @@ Jay.Leno.2012.02.17.Dave.Salmoni.720p.HDTV.x264-BAJSKORV http://nzb.su/details/3049a9e867abda77a1423fbedbabec7c - http://nzb.su/getnzb/3049a9e867abda77a1423fbedbabec7c.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/3049a9e867abda77a1423fbedbabec7c.nzb&i=37292&r=xxx http://nzb.su/details/3049a9e867abda77a1423fbedbabec7c#comments Mon, 27 Feb 2012 01:54:07 -0500 TV > HD Jay.Leno.2012.02.17.Dave.Salmoni.720p.HDTV.x264-BAJSKORV - + @@ -1257,13 +1257,13 @@ Jay.Leno.2012.02.14.Tyler.Perry.720p.HDTV.x264-BAJSKORV http://nzb.su/details/886721cfee04dc96c56352344103c233 - http://nzb.su/getnzb/886721cfee04dc96c56352344103c233.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/886721cfee04dc96c56352344103c233.nzb&i=37292&r=xxx http://nzb.su/details/886721cfee04dc96c56352344103c233#comments Mon, 27 Feb 2012 01:48:31 -0500 TV > HD Jay.Leno.2012.02.14.Tyler.Perry.720p.HDTV.x264-BAJSKORV - + @@ -1276,13 +1276,13 @@ Jay.Leno.2012.02.06.Dwayne.Johnson.720p.HDTV.x264-BAJSKORV http://nzb.su/details/679282512d2678beccab18dba03a62a8 - http://nzb.su/getnzb/679282512d2678beccab18dba03a62a8.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/679282512d2678beccab18dba03a62a8.nzb&i=37292&r=xxx http://nzb.su/details/679282512d2678beccab18dba03a62a8#comments Mon, 27 Feb 2012 01:48:31 -0500 TV > HD Jay.Leno.2012.02.06.Dwayne.Johnson.720p.HDTV.x264-BAJSKORV - + @@ -1295,13 +1295,13 @@ Jimmy.Fallon.2012.02.22.Alan.Alda.720p.HDTV.x264-BAJSKORV http://nzb.su/details/192efe2d4ee53faf05196c0212510b59 - http://nzb.su/getnzb/192efe2d4ee53faf05196c0212510b59.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/192efe2d4ee53faf05196c0212510b59.nzb&i=37292&r=xxx http://nzb.su/details/192efe2d4ee53faf05196c0212510b59#comments Mon, 27 Feb 2012 01:46:52 -0500 TV > HD Jimmy.Fallon.2012.02.22.Alan.Alda.720p.HDTV.x264-BAJSKORV - + @@ -1314,13 +1314,13 @@ Jimmy.Fallon.2012.02.21.Tyler.Perry.720p.HDTV.x264-BAJSKORV http://nzb.su/details/088616fa4b9138149a21af9354900d98 - http://nzb.su/getnzb/088616fa4b9138149a21af9354900d98.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/088616fa4b9138149a21af9354900d98.nzb&i=37292&r=xxx http://nzb.su/details/088616fa4b9138149a21af9354900d98#comments Mon, 27 Feb 2012 01:46:52 -0500 TV > HD Jimmy.Fallon.2012.02.21.Tyler.Perry.720p.HDTV.x264-BAJSKORV - + @@ -1333,13 +1333,13 @@ Eastbound.and.Down.S03E02.HDTV.XviD-2HD http://nzb.su/details/eb106eb8de8f7d49259b61bab732e798 - http://nzb.su/getnzb/eb106eb8de8f7d49259b61bab732e798.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/eb106eb8de8f7d49259b61bab732e798.nzb&i=37292&r=xxx http://nzb.su/details/eb106eb8de8f7d49259b61bab732e798#comments Mon, 27 Feb 2012 01:44:42 -0500 TV > SD Eastbound.and.Down.S03E02.HDTV.XviD-2HD - + @@ -1352,13 +1352,13 @@ Jimmy.Fallon.2012.02.07.Harry.Connick.Jr.720p.HDTV.x264-BAJSKORV http://nzb.su/details/e0c0f698784621ca39df1d5f69e3205b - http://nzb.su/getnzb/e0c0f698784621ca39df1d5f69e3205b.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/e0c0f698784621ca39df1d5f69e3205b.nzb&i=37292&r=xxx http://nzb.su/details/e0c0f698784621ca39df1d5f69e3205b#comments Mon, 27 Feb 2012 01:44:42 -0500 TV > HD Jimmy.Fallon.2012.02.07.Harry.Connick.Jr.720p.HDTV.x264-BAJSKORV - + @@ -1371,13 +1371,13 @@ Jimmy.Fallon.2012.02.20.Anjelica.Houston.720p.HDTV.x264-BAJSKORV http://nzb.su/details/e9f9cac00055872b57b49493ed21bc02 - http://nzb.su/getnzb/e9f9cac00055872b57b49493ed21bc02.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/e9f9cac00055872b57b49493ed21bc02.nzb&i=37292&r=xxx http://nzb.su/details/e9f9cac00055872b57b49493ed21bc02#comments Mon, 27 Feb 2012 01:44:42 -0500 TV > HD Jimmy.Fallon.2012.02.20.Anjelica.Houston.720p.HDTV.x264-BAJSKORV - + @@ -1390,13 +1390,13 @@ Jimmy.Fallon.2012.02.17.Ricky.Gervais.720p.HDTV.x264-BAJSKORV http://nzb.su/details/0ac4fb081cfbbf5e1230109ce538ed25 - http://nzb.su/getnzb/0ac4fb081cfbbf5e1230109ce538ed25.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/0ac4fb081cfbbf5e1230109ce538ed25.nzb&i=37292&r=xxx http://nzb.su/details/0ac4fb081cfbbf5e1230109ce538ed25#comments Mon, 27 Feb 2012 01:42:59 -0500 TV > HD Jimmy.Fallon.2012.02.17.Ricky.Gervais.720p.HDTV.x264-BAJSKORV - + @@ -1409,13 +1409,13 @@ Cartoon Network Hall of Game Awards 2012 1080i HDTV DD5.1 MPEG2-TrollHD http://nzb.su/details/06b110b77e4a0e34ac3dc98d2102b8f7 - http://nzb.su/getnzb/06b110b77e4a0e34ac3dc98d2102b8f7.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/06b110b77e4a0e34ac3dc98d2102b8f7.nzb&i=37292&r=xxx http://nzb.su/details/06b110b77e4a0e34ac3dc98d2102b8f7#comments Mon, 27 Feb 2012 02:10:29 -0500 TV > HD Cartoon Network Hall of Game Awards 2012 1080i HDTV DD5.1 MPEG2-TrollHD - + @@ -1428,13 +1428,13 @@ Jimmy.Fallon.2012.02.15.Greg.Kinnear.720p.HDTV.x264-BAJSKORV http://nzb.su/details/c2d4dba128f03c050ee96e6c5afd48c7 - http://nzb.su/getnzb/c2d4dba128f03c050ee96e6c5afd48c7.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/c2d4dba128f03c050ee96e6c5afd48c7.nzb&i=37292&r=xxx http://nzb.su/details/c2d4dba128f03c050ee96e6c5afd48c7#comments Mon, 27 Feb 2012 01:42:59 -0500 TV > HD Jimmy.Fallon.2012.02.15.Greg.Kinnear.720p.HDTV.x264-BAJSKORV - + @@ -1447,13 +1447,13 @@ Jimmy.Fallon.2012.02.14.Donald.Trump.720p.HDTV.x264-BAJSKORV http://nzb.su/details/8f5a0af48e56d4c7ce8740675336377d - http://nzb.su/getnzb/8f5a0af48e56d4c7ce8740675336377d.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/8f5a0af48e56d4c7ce8740675336377d.nzb&i=37292&r=xxx http://nzb.su/details/8f5a0af48e56d4c7ce8740675336377d#comments Mon, 27 Feb 2012 01:41:19 -0500 TV > HD Jimmy.Fallon.2012.02.14.Donald.Trump.720p.HDTV.x264-BAJSKORV - + @@ -1466,13 +1466,13 @@ Jimmy.Fallon.2012.02.13.Nicolas.Cage.720p.HDTV.x264-BAJSKORV http://nzb.su/details/20d941c07d7c45080bbd97771461a9ab - http://nzb.su/getnzb/20d941c07d7c45080bbd97771461a9ab.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/20d941c07d7c45080bbd97771461a9ab.nzb&i=37292&r=xxx http://nzb.su/details/20d941c07d7c45080bbd97771461a9ab#comments Mon, 27 Feb 2012 01:39:49 -0500 TV > HD Jimmy.Fallon.2012.02.13.Nicolas.Cage.720p.HDTV.x264-BAJSKORV - + @@ -1485,13 +1485,13 @@ Jimmy.Fallon.2012.02.06.The.Best.Of.720p.HDTV.x264-BAJSKORV http://nzb.su/details/eb88e1253cfa159f75a17391f62bcf33 - http://nzb.su/getnzb/eb88e1253cfa159f75a17391f62bcf33.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/eb88e1253cfa159f75a17391f62bcf33.nzb&i=37292&r=xxx http://nzb.su/details/eb88e1253cfa159f75a17391f62bcf33#comments Mon, 27 Feb 2012 01:34:11 -0500 TV > HD Jimmy.Fallon.2012.02.06.The.Best.Of.720p.HDTV.x264-BAJSKORV - + @@ -1504,13 +1504,13 @@ Jimmy.Fallon.2012.02.02.Taylor.Lautner.720p.HDTV.x264-BAJSKORV http://nzb.su/details/c681e94346b715f485eec20ffc9844c2 - http://nzb.su/getnzb/c681e94346b715f485eec20ffc9844c2.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/c681e94346b715f485eec20ffc9844c2.nzb&i=37292&r=xxx http://nzb.su/details/c681e94346b715f485eec20ffc9844c2#comments Mon, 27 Feb 2012 01:32:29 -0500 TV > HD Jimmy.Fallon.2012.02.02.Taylor.Lautner.720p.HDTV.x264-BAJSKORV - + @@ -1523,13 +1523,13 @@ The.Apprentice.US.S12E02.HDTV.XviD-2HD http://nzb.su/details/f3e00a427211bbbf667d184fcf05b94d - http://nzb.su/getnzb/f3e00a427211bbbf667d184fcf05b94d.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/f3e00a427211bbbf667d184fcf05b94d.nzb&i=37292&r=xxx http://nzb.su/details/f3e00a427211bbbf667d184fcf05b94d#comments Mon, 27 Feb 2012 01:06:20 -0500 TV > SD The.Apprentice.US.S12E02.HDTV.XviD-2HD - + @@ -1542,13 +1542,13 @@ Heartland.CA.S05E14.720p.HDTV.x264-2HD http://nzb.su/details/cb901e996ce1286c938d9400350a2983 - http://nzb.su/getnzb/cb901e996ce1286c938d9400350a2983.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/cb901e996ce1286c938d9400350a2983.nzb&i=37292&r=xxx http://nzb.su/details/cb901e996ce1286c938d9400350a2983#comments Mon, 27 Feb 2012 01:04:41 -0500 TV > HD Heartland.CA.S05E14.720p.HDTV.x264-2HD - + @@ -1561,13 +1561,13 @@ time.out.s13e03.pdtv.x264-d2v http://nzb.su/details/1cb4b15820ea8ee09682da493216d7f0 - http://nzb.su/getnzb/1cb4b15820ea8ee09682da493216d7f0.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/1cb4b15820ea8ee09682da493216d7f0.nzb&i=37292&r=xxx http://nzb.su/details/1cb4b15820ea8ee09682da493216d7f0#comments Mon, 27 Feb 2012 00:57:29 -0500 TV > SD time.out.s13e03.pdtv.x264-d2v - + @@ -1580,13 +1580,13 @@ lyxfallan.s12e04.proper.pdtv.x264-d2v http://nzb.su/details/caf3dc9a99fc36167f49183da991e652 - http://nzb.su/getnzb/caf3dc9a99fc36167f49183da991e652.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/caf3dc9a99fc36167f49183da991e652.nzb&i=37292&r=xxx http://nzb.su/details/caf3dc9a99fc36167f49183da991e652#comments Mon, 27 Feb 2012 00:52:49 -0500 TV > SD lyxfallan.s12e04.proper.pdtv.x264-d2v - + @@ -1599,13 +1599,13 @@ karatefylla.s02e01.pdtv.x264-d2v http://nzb.su/details/e8a70fb2b81e715d8650bcbdef7ea55e - http://nzb.su/getnzb/e8a70fb2b81e715d8650bcbdef7ea55e.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/e8a70fb2b81e715d8650bcbdef7ea55e.nzb&i=37292&r=xxx http://nzb.su/details/e8a70fb2b81e715d8650bcbdef7ea55e#comments Mon, 27 Feb 2012 00:34:28 -0500 TV > SD karatefylla.s02e01.pdtv.x264-d2v - + @@ -1618,13 +1618,13 @@ The.Walking.Dead.S02E10.iNTERNAL.720p.HDTV.x264-2HD http://nzb.su/details/0dd3bc43ec5856a34aff3cbed4e5def3 - http://nzb.su/getnzb/0dd3bc43ec5856a34aff3cbed4e5def3.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/0dd3bc43ec5856a34aff3cbed4e5def3.nzb&i=37292&r=xxx http://nzb.su/details/0dd3bc43ec5856a34aff3cbed4e5def3#comments Mon, 27 Feb 2012 00:30:53 -0500 TV > HD The.Walking.Dead.S02E10.iNTERNAL.720p.HDTV.x264-2HD - + @@ -1637,13 +1637,13 @@ How.The.Celts.Saved.Britain.S01E02.BDRip.XviD-SPRiNTER http://nzb.su/details/7e66256948969eedc50acfa16dc25336 - http://nzb.su/getnzb/7e66256948969eedc50acfa16dc25336.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/7e66256948969eedc50acfa16dc25336.nzb&i=37292&r=xxx http://nzb.su/details/7e66256948969eedc50acfa16dc25336#comments Mon, 27 Feb 2012 00:02:54 -0500 TV > SD How.The.Celts.Saved.Britain.S01E02.BDRip.XviD-SPRiNTER - + @@ -1656,13 +1656,13 @@ How.The.Celts.Saved.Britain.S01E01.BDRip.XviD-SPRiNTER http://nzb.su/details/c3421676f28ebe6584b4ba4237d2531b - http://nzb.su/getnzb/c3421676f28ebe6584b4ba4237d2531b.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/c3421676f28ebe6584b4ba4237d2531b.nzb&i=37292&r=xxx http://nzb.su/details/c3421676f28ebe6584b4ba4237d2531b#comments Mon, 27 Feb 2012 00:02:54 -0500 TV > SD How.The.Celts.Saved.Britain.S01E01.BDRip.XviD-SPRiNTER - + @@ -1675,13 +1675,13 @@ Wanna.BEn.S02E02.PDTV.XviD-FiHTV http://nzb.su/details/a297836314a5aa5947957babc9277148 - http://nzb.su/getnzb/a297836314a5aa5947957babc9277148.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/a297836314a5aa5947957babc9277148.nzb&i=37292&r=xxx http://nzb.su/details/a297836314a5aa5947957babc9277148#comments Sun, 26 Feb 2012 23:59:18 -0500 TV > SD Wanna.BEn.S02E02.PDTV.XviD-FiHTV - + @@ -1694,13 +1694,13 @@ Ax.Men.S05E07.Wake-Up.Call.720p.HDTV.x264-MOMENTUM http://nzb.su/details/1e28429ef8dff9ef00df2927cd381177 - http://nzb.su/getnzb/1e28429ef8dff9ef00df2927cd381177.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/1e28429ef8dff9ef00df2927cd381177.nzb&i=37292&r=xxx http://nzb.su/details/1e28429ef8dff9ef00df2927cd381177#comments Sun, 26 Feb 2012 23:44:08 -0500 TV > HD Ax.Men.S05E07.Wake-Up.Call.720p.HDTV.x264-MOMENTUM - + @@ -1713,13 +1713,13 @@ Heartland.CA.S05E14.HDTV.XviD-2HD http://nzb.su/details/7726d5073f24f2ad0593cfee6619621d - http://nzb.su/getnzb/7726d5073f24f2ad0593cfee6619621d.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/7726d5073f24f2ad0593cfee6619621d.nzb&i=37292&r=xxx http://nzb.su/details/7726d5073f24f2ad0593cfee6619621d#comments Sun, 26 Feb 2012 23:38:49 -0500 TV > SD Heartland.CA.S05E14.HDTV.XviD-2HD - + @@ -1732,13 +1732,13 @@ Parallel Series 2 (MOTE028D)-WEB-2012-dL http://nzb.su/details/1f182287ce20e045411648b8d60e300c - http://nzb.su/getnzb/1f182287ce20e045411648b8d60e300c.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/1f182287ce20e045411648b8d60e300c.nzb&i=37292&r=xxx http://nzb.su/details/1f182287ce20e045411648b8d60e300c#comments Sun, 26 Feb 2012 23:36:04 -0500 TV > SD Parallel Series 2 (MOTE028D)-WEB-2012-dL - + @@ -1751,13 +1751,13 @@ 7.Days.NZ.S04E02.PDTV.XviD-FiHTV http://nzb.su/details/14542c22bbc1e1a584332ebf5f3487d4 - http://nzb.su/getnzb/14542c22bbc1e1a584332ebf5f3487d4.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/14542c22bbc1e1a584332ebf5f3487d4.nzb&i=37292&r=xxx http://nzb.su/details/14542c22bbc1e1a584332ebf5f3487d4#comments Sun, 26 Feb 2012 23:32:11 -0500 TV > SD 7.Days.NZ.S04E02.PDTV.XviD-FiHTV - + @@ -1770,13 +1770,13 @@ Finding.Bigfoot.S02E08.Finding.Bigfoot.Special.HDTV.XviD-FQM http://nzb.su/details/058d8b676adc765a2fc7c3260066958f - http://nzb.su/getnzb/058d8b676adc765a2fc7c3260066958f.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/058d8b676adc765a2fc7c3260066958f.nzb&i=37292&r=xxx http://nzb.su/details/058d8b676adc765a2fc7c3260066958f#comments Sun, 26 Feb 2012 23:20:28 -0500 TV > SD Finding.Bigfoot.S02E08.Finding.Bigfoot.Special.HDTV.XviD-FQM - + @@ -1789,13 +1789,13 @@ Oscars.Red.Carpet.Live.2012.720p.HDTV.x264-2HD http://nzb.su/details/5f8772bab2282f1f6938614114a71fb4 - http://nzb.su/getnzb/5f8772bab2282f1f6938614114a71fb4.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/5f8772bab2282f1f6938614114a71fb4.nzb&i=37292&r=xxx http://nzb.su/details/5f8772bab2282f1f6938614114a71fb4#comments Sun, 26 Feb 2012 23:15:19 -0500 TV > HD Oscars.Red.Carpet.Live.2012.720p.HDTV.x264-2HD - + @@ -1808,13 +1808,13 @@ The.Apprentice.S12E02.720p.HDTV.x264-BAJSKORV http://nzb.su/details/884783386e9802e6ed4ca85edf1601b5 - http://nzb.su/getnzb/884783386e9802e6ed4ca85edf1601b5.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/884783386e9802e6ed4ca85edf1601b5.nzb&i=37292&r=xxx http://nzb.su/details/884783386e9802e6ed4ca85edf1601b5#comments Sun, 26 Feb 2012 23:13:20 -0500 TV > HD The.Apprentice.S12E02.720p.HDTV.x264-BAJSKORV - + @@ -1827,13 +1827,13 @@ The.Walking.Dead.S02E10.HDTV.x264-ASAP http://nzb.su/details/ea5a6405af227988216a5157db8229db - http://nzb.su/getnzb/ea5a6405af227988216a5157db8229db.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/ea5a6405af227988216a5157db8229db.nzb&i=37292&r=xxx http://nzb.su/details/ea5a6405af227988216a5157db8229db#comments Sun, 26 Feb 2012 23:10:28 -0500 TV > SD The.Walking.Dead.S02E10.HDTV.x264-ASAP - + @@ -1846,13 +1846,13 @@ The.Walking.Dead.S02E10.720p.HDTV.x264-IMMERSE http://nzb.su/details/6860ebbe38724e58747edf7a804cbadb - http://nzb.su/getnzb/6860ebbe38724e58747edf7a804cbadb.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/6860ebbe38724e58747edf7a804cbadb.nzb&i=37292&r=xxx http://nzb.su/details/6860ebbe38724e58747edf7a804cbadb#comments Sun, 26 Feb 2012 23:10:28 -0500 TV > HD The.Walking.Dead.S02E10.720p.HDTV.x264-IMMERSE - + @@ -1865,13 +1865,13 @@ The.Apprentice.S12E02.HDTV.x264-BAJSKORV http://nzb.su/details/b16397d84630fa2f6d5a140f3013a998 - http://nzb.su/getnzb/b16397d84630fa2f6d5a140f3013a998.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/b16397d84630fa2f6d5a140f3013a998.nzb&i=37292&r=xxx http://nzb.su/details/b16397d84630fa2f6d5a140f3013a998#comments Sun, 26 Feb 2012 23:03:46 -0500 TV > SD The.Apprentice.S12E02.HDTV.x264-BAJSKORV - + @@ -1884,13 +1884,13 @@ Ax.Men.S05E07.Wake-Up.Call.HDTV.x264-MOMENTUM http://nzb.su/details/db7c5f361814f4a910ac0b73d30f7468 - http://nzb.su/getnzb/db7c5f361814f4a910ac0b73d30f7468.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/db7c5f361814f4a910ac0b73d30f7468.nzb&i=37292&r=xxx http://nzb.su/details/db7c5f361814f4a910ac0b73d30f7468#comments Sun, 26 Feb 2012 22:53:44 -0500 TV > SD Ax.Men.S05E07.Wake-Up.Call.HDTV.x264-MOMENTUM - + @@ -1903,13 +1903,13 @@ The.Amazing.Race.S20E02.HDTV.XviD-2HD http://nzb.su/details/a3416403c5ff159657199fde24c1fb7a - http://nzb.su/getnzb/a3416403c5ff159657199fde24c1fb7a.nzb&i=37292&r=524d129cbd8a329be916e0573d10be5c + http://nzb.su/getnzb/a3416403c5ff159657199fde24c1fb7a.nzb&i=37292&r=xxx http://nzb.su/details/a3416403c5ff159657199fde24c1fb7a#comments Sun, 26 Feb 2012 22:40:59 -0500 TV > SD The.Amazing.Race.S20E02.HDTV.XviD-2HD - + diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/IndexerCheckFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/IndexerCheckFixture.cs index 15e7754ad..6eb1d4204 100644 --- a/src/NzbDrone.Core.Test/HealthCheck/Checks/IndexerCheckFixture.cs +++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/IndexerCheckFixture.cs @@ -86,7 +86,7 @@ public void should_return_ok_when_multiple_indexers_are_enabled() indexer1.SetupGet(s => s.SupportsRss).Returns(true); indexer1.SetupGet(s => s.SupportsSearch).Returns(true); - var indexer2 = Mocker.GetMock(); + var indexer2 = new Moq.Mock(); indexer2.SetupGet(s => s.SupportsRss).Returns(true); indexer2.SetupGet(s => s.SupportsSearch).Returns(false); diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs index 7780caa21..ffc9f68a1 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs @@ -17,18 +17,19 @@ namespace NzbDrone.Core.Test.IndexerSearchTests { public class NzbSearchServiceFixture : CoreTest { + private Mock _mockIndexer; private Series _xemSeries; private List _xemEpisodes; [SetUp] public void SetUp() { - var indexer = Mocker.GetMock(); - indexer.SetupGet(s => s.SupportsSearch).Returns(true); + _mockIndexer = Mocker.GetMock(); + _mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true); Mocker.GetMock() .Setup(s => s.SearchEnabled()) - .Returns(new List { indexer.Object }); + .Returns(new List { _mockIndexer.Object }); Mocker.GetMock() .Setup(s => s.GetSearchDecision(It.IsAny>(), It.IsAny())) @@ -97,19 +98,16 @@ private List WatchForSearchCriteria() { var result = new List(); - Mocker.GetMock() - .Setup(v => v.Fetch(It.IsAny(), It.IsAny())) - .Callback((i, s) => result.Add(s)) + _mockIndexer.Setup(v => v.Fetch(It.IsAny())) + .Callback(s => result.Add(s)) .Returns(new List()); - Mocker.GetMock() - .Setup(v => v.Fetch(It.IsAny(), It.IsAny())) - .Callback((i, s) => result.Add(s)) + _mockIndexer.Setup(v => v.Fetch(It.IsAny())) + .Callback(s => result.Add(s)) .Returns(new List()); - Mocker.GetMock() - .Setup(v => v.Fetch(It.IsAny(), It.IsAny())) - .Callback((i, s) => result.Add(s)) + _mockIndexer.Setup(v => v.Fetch(It.IsAny())) + .Callback(s => result.Add(s)) .Returns(new List()); return result; diff --git a/src/NzbDrone.Core.Test/IndexerTests/AnimezbTests/AnimezbFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/AnimezbTests/AnimezbFixture.cs new file mode 100644 index 000000000..660615364 --- /dev/null +++ b/src/NzbDrone.Core.Test/IndexerTests/AnimezbTests/AnimezbFixture.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.Animezb; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.Test.IndexerTests.AnimezbTests +{ + [TestFixture] + public class AnimezbFixture : CoreTest + { + [SetUp] + public void Setup() + { + Subject.Definition = new IndexerDefinition() + { + Name = "Animezb", + Settings = new NullConfig() + }; + + } + + [Test] + public void should_parse_recent_feed_from_Animezb() + { + Assert.Inconclusive("Waiting for animezb to get back up."); + + var recentFeed = ReadAllText(@"Files/RSS/Animezb.xml"); + + Mocker.GetMock() + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); + + var releases = Subject.FetchRecent(); + + releases.Should().HaveCount(3); + + var releaseInfo = releases.First(); + + //releaseInfo.Title.Should().Be("[Vivid] Hanayamata - 10 [A33D6606]"); + releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet); + //releaseInfo.DownloadUrl.Should().Be("http://fanzub.com/nzb/296464/Vivid%20Hanayamata%20-%2010.nzb"); + releaseInfo.InfoUrl.Should().BeNullOrEmpty(); + releaseInfo.CommentUrl.Should().BeNullOrEmpty(); + releaseInfo.Indexer.Should().Be(Subject.Definition.Name); + //releaseInfo.PublishDate.Should().Be(DateTime.Parse("2014/09/13 12:56:53")); + //releaseInfo.Size.Should().Be(556246858); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs index efdcfab32..fd471c0b2 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/BasicRssParserFixture.cs @@ -5,7 +5,7 @@ namespace NzbDrone.Core.Test.IndexerTests { - public class BasicRssParserFixture : CoreTest + public class BasicRssParserFixture : CoreTest { [TestCase("5.64 GB", 6055903887)] [TestCase("5.54 GiB", 5948529705)] @@ -16,7 +16,7 @@ public class BasicRssParserFixture : CoreTest [TestCase("845 MB", 886046720)] public void parse_size(string sizeString, long expectedSize) { - var result = RssParserBase.ParseSize(sizeString, true); + var result = RssParser.ParseSize(sizeString, true); result.Should().Be(expectedSize); } diff --git a/src/NzbDrone.Core.Test/IndexerTests/FanzubTests/FanzubFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/FanzubTests/FanzubFixture.cs new file mode 100644 index 000000000..86cfa2960 --- /dev/null +++ b/src/NzbDrone.Core.Test/IndexerTests/FanzubTests/FanzubFixture.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.Fanzub; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.Test.IndexerTests.FanzubTests +{ + [TestFixture] + public class FanzubFixture : CoreTest + { + [SetUp] + public void Setup() + { + Subject.Definition = new IndexerDefinition() + { + Name = "Fanzub", + Settings = new NullConfig() + }; + } + + [Test] + public void should_parse_recent_feed_from_fanzub() + { + var recentFeed = ReadAllText(@"Files/RSS/fanzub.xml"); + + Mocker.GetMock() + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); + + var releases = Subject.FetchRecent(); + + releases.Should().HaveCount(3); + + var releaseInfo = releases.First(); + + releaseInfo.Title.Should().Be("[Vivid] Hanayamata - 10 [A33D6606]"); + releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet); + releaseInfo.DownloadUrl.Should().Be("http://fanzub.com/nzb/296464/Vivid%20Hanayamata%20-%2010.nzb"); + releaseInfo.InfoUrl.Should().BeNullOrEmpty(); + releaseInfo.CommentUrl.Should().BeNullOrEmpty(); + releaseInfo.Indexer.Should().Be(Subject.Definition.Name); + releaseInfo.PublishDate.Should().Be(DateTime.Parse("2014/09/13 12:56:53")); + releaseInfo.Size.Should().Be(556246858); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs index e27c02492..eceb25b11 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs @@ -20,9 +20,9 @@ public void Setup() { _indexers = new List(); - _indexers.Add(Mocker.GetMock().Object); - _indexers.Add(new Omgwtfnzbs()); - _indexers.Add(new Wombles()); + _indexers.Add(Mocker.Resolve()); + _indexers.Add(Mocker.Resolve()); + _indexers.Add(Mocker.Resolve()); Mocker.SetConstant>(_indexers); } diff --git a/src/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs b/src/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs index 83047365c..fd6a7b9c2 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests { [IntegrationTest] - public class IndexerIntegrationTests : CoreTest + public class IndexerIntegrationTests : CoreTest { [SetUp] public void SetUp() @@ -24,15 +24,13 @@ public void SetUp() [Test] public void wombles_rss() { - var indexer = new Wombles(); - - indexer.Definition = new IndexerDefinition + Subject.Definition = new IndexerDefinition { Name = "Wombles", Settings = NullConfig.Instance }; - var result = Subject.FetchRss(indexer); + var result = Subject.FetchRecent(); ValidateResult(result, skipSize: true, skipInfo: true); } diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs new file mode 100644 index 000000000..112af3104 --- /dev/null +++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.Newznab; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.Test.IndexerTests.NewznabTests +{ + [TestFixture] + public class NewznabFixture : CoreTest + { + [SetUp] + public void Setup() + { + Subject.Definition = new IndexerDefinition() + { + Name = "Newznab", + Settings = new NewznabSettings() + { + Url = "http://indexer.local/", + Categories = new Int32[] { 1 } + } + }; + } + + [Test] + public void should_parse_recent_feed_from_newznab_nzb_su() + { + var recentFeed = ReadAllText(@"Files/RSS/newznab_nzb_su.xml"); + + Mocker.GetMock() + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); + + var releases = Subject.FetchRecent(); + + releases.Should().HaveCount(100); + + var releaseInfo = releases.First(); + + releaseInfo.Title.Should().Be("White.Collar.S03E05.720p.HDTV.X264-DIMENSION"); + releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet); + releaseInfo.DownloadUrl.Should().Be("http://nzb.su/getnzb/24967ef4c2e26296c65d3bbfa97aa8fe.nzb&i=37292&r=xxx"); + releaseInfo.InfoUrl.Should().Be("http://nzb.su/details/24967ef4c2e26296c65d3bbfa97aa8fe"); + releaseInfo.CommentUrl.Should().Be("http://nzb.su/details/24967ef4c2e26296c65d3bbfa97aa8fe#comments"); + releaseInfo.Indexer.Should().Be(Subject.Definition.Name); + releaseInfo.PublishDate.Should().Be(DateTime.Parse("2012/02/27 16:09:39")); + releaseInfo.Size.Should().Be(1183105773); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs new file mode 100644 index 000000000..b7c7d070e --- /dev/null +++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Indexers.Newznab; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.IndexerTests.NewznabTests +{ + public class NewznabRequestGeneratorFixture : CoreTest + { + AnimeEpisodeSearchCriteria _animeSearchCriteria; + + [SetUp] + public void SetUp() + { + Subject.Settings = new NewznabSettings() + { + Url = "http://127.0.0.1:1234/", + Categories = new [] { 1, 2 }, + AnimeCategories = new [] { 3, 4 }, + ApiKey = "abcd", + }; + + _animeSearchCriteria = new AnimeEpisodeSearchCriteria() + { + SceneTitles = new List() { "Monkey+Island" }, + AbsoluteEpisodeNumber = 100 + }; + } + + [Test] + public void should_return_one_page_for_feed() + { + var results = Subject.GetRecentRequests(); + + results.Should().HaveCount(1); + + var pages = results.First().Take(10).ToList(); + + pages.Should().HaveCount(1); + } + + [Test] + public void should_use_all_categories_for_feed() + { + var results = Subject.GetRecentRequests(); + + results.Should().HaveCount(1); + + var page = results.First().First(); + + page.Url.Query.Should().Contain("&cat=1,2,3,4&"); + } + + [Test] + public void should_not_have_duplicate_categories() + { + Subject.Settings.Categories = new[] { 1, 2, 3 }; + + var results = Subject.GetRecentRequests(); + + results.Should().HaveCount(1); + + var page = results.First().First(); + + page.Url.Query.Should().Contain("&cat=1,2,3,4&"); + } + + [Test] + public void should_use_only_anime_categories_for_anime_search() + { + var results = Subject.GetSearchRequests(_animeSearchCriteria); + + results.Should().HaveCount(1); + + var page = results.First().First(); + + page.Url.Query.Should().Contain("&cat=3,4&"); + } + + [Test] + public void should_use_mode_search_for_anime() + { + var results = Subject.GetSearchRequests(_animeSearchCriteria); + + results.Should().HaveCount(1); + + var page = results.First().First(); + + page.Url.Query.Should().Contain("?t=search&"); + } + + [Test] + public void should_return_subsequent_pages() + { + var results = Subject.GetSearchRequests(_animeSearchCriteria); + + results.Should().HaveCount(1); + + var pages = results.First().Take(3).ToList(); + + pages[0].Url.Query.Should().Contain("&offset=0&"); + pages[1].Url.Query.Should().Contain("&offset=100&"); + pages[2].Url.Query.Should().Contain("&offset=200&"); + } + + [Test] + public void should_not_get_unlimited_pages() + { + var results = Subject.GetSearchRequests(_animeSearchCriteria); + + results.Should().HaveCount(1); + + var pages = results.First().Take(500).ToList(); + + pages.Count.Should().BeLessThan(500); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs new file mode 100644 index 000000000..f7048e92b --- /dev/null +++ b/src/NzbDrone.Core.Test/IndexerTests/OmgwtfnzbsTests/OmgwtfnzbsFixture.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.Omgwtfnzbs; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.Test.IndexerTests.OmgwtfnzbsTests +{ + [TestFixture] + public class OmgwtfnzbsFixture : CoreTest + { + [SetUp] + public void Setup() + { + Subject.Definition = new IndexerDefinition() + { + Name = "Omgwtfnzbs", + Settings = new OmgwtfnzbsSettings() + { + ApiKey = "xxx", + Username = "me@my.domain" + } + }; + } + + [Test] + public void should_parse_recent_feed_from_omgwtfnzbs() + { + var recentFeed = ReadAllText(@"Files/RSS/omgwtfnzbs.xml"); + + Mocker.GetMock() + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); + + var releases = Subject.FetchRecent(); + + releases.Should().HaveCount(100); + + var releaseInfo = releases.First(); + + releaseInfo.Title.Should().Be("Stephen.Fry.Gadget.Man.S01E05.HDTV.x264-C4TV"); + releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet); + releaseInfo.DownloadUrl.Should().Be("http://api.omgwtfnzbs.org/sn.php?id=OAl4g&user=nzbdrone&api=nzbdrone"); + releaseInfo.InfoUrl.Should().Be("http://omgwtfnzbs.org/details.php?id=OAl4g"); + releaseInfo.CommentUrl.Should().BeNullOrEmpty(); + releaseInfo.Indexer.Should().Be(Subject.Definition.Name); + releaseInfo.PublishDate.Should().Be(DateTime.Parse("2012/12/17 23:30:13")); + releaseInfo.Size.Should().Be(236822906); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/SeasonSearchFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/SeasonSearchFixture.cs index 1b71181d1..333499ba8 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/SeasonSearchFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/SeasonSearchFixture.cs @@ -3,10 +3,13 @@ using FizzWare.NBuilder; using FluentValidation.Results; using Moq; +using NLog; using NUnit.Framework; using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Tv; @@ -15,7 +18,7 @@ namespace NzbDrone.Core.Test.IndexerTests { [TestFixture] - public class SeasonSearchFixture : TestBase + public class SeasonSearchFixture : TestBase { private Series _series; @@ -25,67 +28,68 @@ public void Setup() _series = Builder.CreateNew().Build(); Mocker.GetMock() - .Setup(o => o.Get(It.IsAny())) + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) .Returns(r => new HttpResponse(r, new HttpHeader(), "")); } - private IndexerBase WithIndexer(bool paging, int resultCount) + private void WithIndexer(bool paging, int resultCount) { + var definition = new IndexerDefinition(); + definition.Name = "Test"; + Subject.Definition = definition; + + Subject._supportedPageSize = paging ? 100 : 0; + + var requestGenerator = Mocker.GetMock(); + Subject._requestGenerator = requestGenerator.Object; + + var requests = Builder.CreateListOfSize(paging ? 100 : 1) + .All() + .WithConstructor(() => new IndexerRequest("http://my.feed.local/", HttpAccept.Rss)) + .With(v => v.HttpRequest.Method = HttpMethod.GET) + .Build(); + + requestGenerator.Setup(s => s.GetSearchRequests(It.IsAny())) + .Returns(new List> { requests }); + + var parser = Mocker.GetMock(); + Subject._parser = parser.Object; + var results = Builder.CreateListOfSize(resultCount) .Build(); - var indexer = Mocker.GetMock>(); - - indexer.Setup(s => s.Parser.Process(It.IsAny(), It.IsAny())) + parser.Setup(s => s.ParseResponse(It.IsAny())) .Returns(results); - - indexer.Setup(s => s.GetSeasonSearchUrls(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(new List { "http://www.nzbdrone.com" }); - - indexer.SetupGet(s => s.SupportedPageSize).Returns(paging ? 100 : 0); - - var definition = new IndexerDefinition(); - definition.Name = "Test"; - - indexer.SetupGet(s => s.Definition) - .Returns(definition); - - return indexer.Object; } [Test] public void should_not_use_offset_if_result_count_is_less_than_90() { - var indexer = WithIndexer(true, 25); - Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitles = new List{_series.Title} }); + WithIndexer(true, 25); - Mocker.GetMock().Verify(v => v.Get(It.IsAny()), Times.Once()); + Subject.Fetch(new SeasonSearchCriteria { Series = _series, SceneTitles = new List{_series.Title} }); + + Mocker.GetMock().Verify(v => v.Execute(It.IsAny()), Times.Once()); } [Test] public void should_not_use_offset_for_sites_that_do_not_support_it() { - var indexer = WithIndexer(false, 125); - Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitles = new List { _series.Title } }); + WithIndexer(false, 125); - Mocker.GetMock().Verify(v => v.Get(It.IsAny()), Times.Once()); + Subject.Fetch(new SeasonSearchCriteria { Series = _series, SceneTitles = new List { _series.Title } }); + + Mocker.GetMock().Verify(v => v.Execute(It.IsAny()), Times.Once()); } [Test] public void should_not_use_offset_if_its_already_tried_10_times() { - var indexer = WithIndexer(true, 100); - Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitles = new List { _series.Title } }); + WithIndexer(true, 100); - Mocker.GetMock().Verify(v => v.Get(It.IsAny()), Times.Exactly(10)); - } - } + Subject.Fetch(new SeasonSearchCriteria { Series = _series, SceneTitles = new List { _series.Title } }); - public class TestIndexerSettings : IProviderConfig - { - public ValidationResult Validate() - { - throw new NotImplementedException(); + Mocker.GetMock().Verify(v => v.Execute(It.IsAny()), Times.Exactly(10)); } } } diff --git a/src/NzbDrone.Core.Test/IndexerTests/TestIndexer.cs b/src/NzbDrone.Core.Test/IndexerTests/TestIndexer.cs new file mode 100644 index 000000000..f7ac837b4 --- /dev/null +++ b/src/NzbDrone.Core.Test/IndexerTests/TestIndexer.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.Test.IndexerTests +{ + public class TestIndexer : HttpIndexerBase + { + public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } } + + public Int32 _supportedPageSize; + public override Int32 PageSize { get { return _supportedPageSize; } } + + public TestIndexer(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, configService, parsingService, logger) + { + + } + + public IIndexerRequestGenerator _requestGenerator; + public override IIndexerRequestGenerator GetRequestGenerator() + { + return _requestGenerator; + } + + public IParseIndexerResponse _parser; + public override IParseIndexerResponse GetParser() + { + return _parser; + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs b/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs new file mode 100644 index 000000000..8b66d17e9 --- /dev/null +++ b/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentValidation.Results; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.Test.IndexerTests +{ + public class TestIndexerSettings : IProviderConfig + { + public ValidationResult Validate() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index d62c68769..a755ffecb 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -174,11 +174,18 @@ + + + + + + + @@ -317,6 +324,10 @@ sqlite3.dll Always + + Always + + App.config @@ -348,7 +359,7 @@ Always - + Always diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index b0e8115d9..304c37222 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -25,7 +25,6 @@ public interface ISearchForNzb public class NzbSearchService : ISearchForNzb { private readonly IIndexerFactory _indexerFactory; - private readonly IFetchFeedFromIndexers _feedFetcher; private readonly ISceneMappingService _sceneMapping; private readonly ISeriesService _seriesService; private readonly IEpisodeService _episodeService; @@ -33,7 +32,6 @@ public class NzbSearchService : ISearchForNzb private readonly Logger _logger; public NzbSearchService(IIndexerFactory indexerFactory, - IFetchFeedFromIndexers feedFetcher, ISceneMappingService sceneMapping, ISeriesService seriesService, IEpisodeService episodeService, @@ -41,7 +39,6 @@ public NzbSearchService(IIndexerFactory indexerFactory, Logger logger) { _indexerFactory = indexerFactory; - _feedFetcher = feedFetcher; _sceneMapping = sceneMapping; _seriesService = seriesService; _episodeService = episodeService; @@ -126,7 +123,7 @@ public List SeasonSearch(int seriesId, int seasonNumber) else searchSpec.EpisodeNumber = episode.SceneEpisodeNumber; - var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); downloadDecisions.AddRange(decisions); } else @@ -134,7 +131,7 @@ public List SeasonSearch(int seriesId, int seasonNumber) var searchSpec = Get(series, sceneSeasonEpisodes.ToList()); searchSpec.SeasonNumber = sceneSeasonEpisodes.Key; - var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); downloadDecisions.AddRange(decisions); } } @@ -144,7 +141,7 @@ public List SeasonSearch(int seriesId, int seasonNumber) var searchSpec = Get(series, episodes); searchSpec.SeasonNumber = seasonNumber; - var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); downloadDecisions.AddRange(decisions); } @@ -175,7 +172,7 @@ private List SearchSingle(Series series, Episode episode) searchSpec.SeasonNumber = episode.SeasonNumber; } - return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } private List SearchDaily(Series series, Episode episode) @@ -184,7 +181,7 @@ private List SearchDaily(Series series, Episode episode) var searchSpec = Get(series, new List{ episode }); searchSpec.AirDate = airDate; - return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } private List SearchAnime(Series series, Episode episode) @@ -202,7 +199,7 @@ private List SearchAnime(Series series, Episode episode) throw new ArgumentOutOfRangeException("AbsoluteEpisodeNumber", "Can not search for an episode absolute episode number of zero"); } - return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } private List SearchSpecial(Series series, List episodes) @@ -213,7 +210,7 @@ private List SearchSpecial(Series series, List episod .SelectMany(e => searchSpec.QueryTitles.Select(title => title + " " + SearchCriteriaBase.GetQueryTitle(e.Title))) .ToArray(); - return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } private List SearchAnimeSeason(Series series, List episodes) diff --git a/src/NzbDrone.Core/Indexers/Animezb/Animezb.cs b/src/NzbDrone.Core/Indexers/Animezb/Animezb.cs index aa5030cb3..a47c6ce83 100644 --- a/src/NzbDrone.Core/Indexers/Animezb/Animezb.cs +++ b/src/NzbDrone.Core/Indexers/Animezb/Animezb.cs @@ -3,101 +3,32 @@ using System.Linq; using System.Text.RegularExpressions; using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers.Animezb { - public class Animezb : IndexerBase + public class Animezb : HttpIndexerBase { - private static readonly Regex RemoveCharactersRegex = new Regex(@"[!?`]", RegexOptions.Compiled); - private static readonly Regex RemoveSingleCharacterRegex = new Regex(@"\b[a-z0-9]\b", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static readonly Regex DuplicateCharacterRegex = new Regex(@"[ +]{2,}", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } } - public override DownloadProtocol Protocol + public Animezb(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, configService, parsingService, logger) { - get - { - return DownloadProtocol.Usenet; - } - } - - public override bool SupportsSearch - { - get - { - return true; - } - } - - public override IParseFeed Parser - { - get - { - return new AnimezbParser(); - } - } - - public override IEnumerable RecentFeed - { - get - { - yield return "https://animezb.com/rss?cat=anime&max=100"; - } - } - - public override IEnumerable GetEpisodeSearchUrls(List titles, int tvRageId, int seasonNumber, int episodeNumber) - { - return new List(); - } - - public override IEnumerable GetSeasonSearchUrls(List titles, int tvRageId, int seasonNumber, int offset) - { - return new List(); - } - - public override IEnumerable GetDailyEpisodeSearchUrls(List titles, int tvRageId, DateTime date) - { - return new List(); - } - - public override IEnumerable GetAnimeEpisodeSearchUrls(List titles, int tvRageId, int absoluteEpisodeNumber) - { - return titles.SelectMany(title => - RecentFeed.Select(url => - String.Format("{0}&q={1}", url, GetSearchQuery(title, absoluteEpisodeNumber)))); } - public override IEnumerable GetSearchUrls(string query, int offset) + public override IIndexerRequestGenerator GetRequestGenerator() { - return new List(); + return new AnimezbRequestGenerator(); } - public override ValidationResult Test() + public override IParseIndexerResponse GetParser() { - return new ValidationResult(); - } - - private String GetSearchQuery(string title, int absoluteEpisodeNumber) - { - var match = RemoveSingleCharacterRegex.Match(title); - - if (match.Success) - { - title = RemoveSingleCharacterRegex.Replace(title, ""); - - //Since we removed a character we need to not wrap it in quotes and hope animedb doesn't give us a million results - return CleanTitle(String.Format("{0}+{1:00}", title, absoluteEpisodeNumber)); - } - - //Wrap the query in quotes and search! - return CleanTitle(String.Format("\"{0}+{1:00}\"", title, absoluteEpisodeNumber)); - } - - private String CleanTitle(String title) - { - title = RemoveCharactersRegex.Replace(title, ""); - return DuplicateCharacterRegex.Replace(title, "+"); + return new RssParser() { UseEnclosureLength = true }; } } } diff --git a/src/NzbDrone.Core/Indexers/Animezb/AnimezbParser.cs b/src/NzbDrone.Core/Indexers/Animezb/AnimezbParser.cs deleted file mode 100644 index 3b7b8639f..000000000 --- a/src/NzbDrone.Core/Indexers/Animezb/AnimezbParser.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; -using System.Linq; - -namespace NzbDrone.Core.Indexers.Animezb -{ - public class AnimezbParser : RssParserBase - { - protected override string GetNzbInfoUrl(XElement item) - { - IEnumerable matches = item.DescendantsAndSelf("link"); - if (matches.Any()) - { - return matches.First().Value; - } - return String.Empty; - } - - protected override long GetSize(XElement item) - { - IEnumerable matches = item.DescendantsAndSelf("enclosure"); - if (matches.Any()) - { - XElement enclosureElement = matches.First(); - return Convert.ToInt64(enclosureElement.Attribute("length").Value); - } - return 0; - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Animezb/AnimezbRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Animezb/AnimezbRequestGenerator.cs new file mode 100644 index 000000000..ca71f0cdd --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Animezb/AnimezbRequestGenerator.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using NzbDrone.Common; +using NzbDrone.Common.Http; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.Indexers.Animezb +{ + public class AnimezbRequestGenerator : IIndexerRequestGenerator + { + private static readonly Regex RemoveCharactersRegex = new Regex(@"[!?`]", RegexOptions.Compiled); + private static readonly Regex RemoveSingleCharacterRegex = new Regex(@"\b[a-z0-9]\b", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex DuplicateCharacterRegex = new Regex(@"[ +]{2,}", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public String BaseUrl { get; set; } + public Int32 PageSize { get; set; } + + public AnimezbRequestGenerator() + { + BaseUrl = "https://animezb.com/rss?cat=anime"; + PageSize = 100; + } + + public virtual IList> GetRecentRequests() + { + var pageableRequests = new List>(); + + pageableRequests.AddIfNotNull(GetPagedRequests(null)); + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(SeasonSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + foreach (var queryTitle in searchCriteria.QueryTitles) + { + var searchQuery = GetSearchQuery(queryTitle, searchCriteria.AbsoluteEpisodeNumber); + + pageableRequests.Add(GetPagedRequests(searchQuery)); + } + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + + private IEnumerable GetPagedRequests(String query) + { + var url = new StringBuilder(); + url.AppendFormat("{0}&max={1}", BaseUrl, PageSize); + + if (query.IsNotNullOrWhiteSpace()) + { + url.AppendFormat("&q={0}", query); + } + + yield return new IndexerRequest(url.ToString(), HttpAccept.Rss); + } + + private String GetSearchQuery(String title, Int32 absoluteEpisodeNumber) + { + var match = RemoveSingleCharacterRegex.Match(title); + + if (match.Success) + { + title = RemoveSingleCharacterRegex.Replace(title, ""); + + //Since we removed a character we need to not wrap it in quotes and hope animedb doesn't give us a million results + return CleanTitle(String.Format("{0}+{1:00}", title, absoluteEpisodeNumber)); + } + + //Wrap the query in quotes and search! + return CleanTitle(String.Format("\"{0}+{1:00}\"", title, absoluteEpisodeNumber)); + } + + private String CleanTitle(String title) + { + title = RemoveCharactersRegex.Replace(title, ""); + return DuplicateCharacterRegex.Replace(title, "+"); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/BasicTorrentRssParser.cs b/src/NzbDrone.Core/Indexers/BasicTorrentRssParser.cs deleted file mode 100644 index 1fa3ec6c6..000000000 --- a/src/NzbDrone.Core/Indexers/BasicTorrentRssParser.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Xml.Linq; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.Indexers -{ - public class BasicTorrentRssParser : RssParserBase - { - protected override ReleaseInfo CreateNewReleaseInfo() - { - return new TorrentInfo(); - } - - protected override ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult) - { - var torrentInfo = (TorrentInfo)currentResult; - - torrentInfo.MagnetUrl = MagnetUrl(item); - torrentInfo.InfoHash = InfoHash(item); - - return torrentInfo; - } - - protected override long GetSize(XElement item) - { - var elementLength = GetTorrentElement(item).Element("contentLength"); - return Convert.ToInt64(elementLength.Value); - } - - protected virtual string MagnetUrl(XElement item) - { - var elementLength = GetTorrentElement(item).Element("magnetURI"); - return elementLength.Value; - } - - protected virtual string InfoHash(XElement item) - { - var elementLength = GetTorrentElement(item).Element("infoHash"); - return elementLength.Value; - } - - private static XElement GetTorrentElement(XElement item) - { - return item.Element("torrent"); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/Exceptions/IndexerException.cs b/src/NzbDrone.Core/Indexers/Exceptions/IndexerException.cs new file mode 100644 index 000000000..8959016df --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Exceptions/IndexerException.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Indexers.Exceptions +{ + public class IndexerException : NzbDroneException + { + private readonly IndexerResponse _indexerResponse; + + public IndexerException(IndexerResponse response, string message, params object[] args) + : base(message, args) + { + } + + public IndexerException(IndexerResponse response, string message) + : base(message) + { + } + + public IndexerResponse Response + { + get { return _indexerResponse; } + } + } +} diff --git a/src/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs b/src/NzbDrone.Core/Indexers/Exceptions/SizeParsingException.cs similarity index 83% rename from src/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs rename to src/NzbDrone.Core/Indexers/Exceptions/SizeParsingException.cs index 549e52f92..6fe085dd4 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/SizeParsingException.cs +++ b/src/NzbDrone.Core/Indexers/Exceptions/SizeParsingException.cs @@ -1,6 +1,6 @@ using NzbDrone.Common.Exceptions; -namespace NzbDrone.Core.Indexers.Newznab +namespace NzbDrone.Core.Indexers.Exceptions { public class SizeParsingException : NzbDroneException { diff --git a/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs b/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs index 703cde63c..bee4f9122 100644 --- a/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs +++ b/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs @@ -3,88 +3,32 @@ using System.Linq; using System.Text.RegularExpressions; using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers.Fanzub { - public class Fanzub : IndexerBase + public class Fanzub : HttpIndexerBase { - private static readonly Regex RemoveCharactersRegex = new Regex(@"[!?`]", RegexOptions.Compiled); + public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } } - public override DownloadProtocol Protocol + public Fanzub(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, configService, parsingService, logger) { - get - { - return DownloadProtocol.Usenet; - } + } - public override bool SupportsSearch + public override IIndexerRequestGenerator GetRequestGenerator() { - get - { - return true; - } + return new FanzubRequestGenerator(); } - public override IParseFeed Parser + public override IParseIndexerResponse GetParser() { - get - { - return new FanzubParser(); - } - } - - public override IEnumerable RecentFeed - { - get - { - yield return "http://fanzub.com/rss/?cat=anime&max=100"; - } - } - - public override IEnumerable GetEpisodeSearchUrls(List titles, int tvRageId, int seasonNumber, int episodeNumber) - { - return new List(); - } - - public override IEnumerable GetSeasonSearchUrls(List titles, int tvRageId, int seasonNumber, int offset) - { - return new List(); - } - - public override IEnumerable GetDailyEpisodeSearchUrls(List titles, int tvRageId, DateTime date) - { - return new List(); - } - - public override IEnumerable GetAnimeEpisodeSearchUrls(List titles, int tvRageId, int absoluteEpisodeNumber) - { - return RecentFeed.Select(url => String.Format("{0}&q={1}", - url, - String.Join("|", titles.SelectMany(title => GetTitleSearchStrings(title, absoluteEpisodeNumber))))); - } - - public override IEnumerable GetSearchUrls(string query, int offset) - { - return new List(); - } - - public override ValidationResult Test() - { - return new ValidationResult(); - } - - private IEnumerable GetTitleSearchStrings(string title, int absoluteEpisodeNumber) - { - var formats = new[] { "{0}%20{1:00}", "{0}%20-%20{1:00}" }; - - return formats.Select(s => "\"" + String.Format(s, CleanTitle(title), absoluteEpisodeNumber) + "\"" ); - } - - private String CleanTitle(String title) - { - return RemoveCharactersRegex.Replace(title, ""); + return new RssParser() { UseEnclosureUrl = true, UseEnclosureLength = true }; } } } diff --git a/src/NzbDrone.Core/Indexers/Fanzub/FanzubParser.cs b/src/NzbDrone.Core/Indexers/Fanzub/FanzubParser.cs deleted file mode 100644 index fe3229ca8..000000000 --- a/src/NzbDrone.Core/Indexers/Fanzub/FanzubParser.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; -using System.Linq; - -namespace NzbDrone.Core.Indexers.Fanzub -{ - public class FanzubParser : RssParserBase - { - protected override string GetNzbInfoUrl(XElement item) - { - IEnumerable matches = item.DescendantsAndSelf("link"); - if (matches.Any()) - { - return matches.First().Value; - } - return String.Empty; - } - - protected override long GetSize(XElement item) - { - IEnumerable matches = item.DescendantsAndSelf("enclosure"); - if (matches.Any()) - { - XElement enclosureElement = matches.First(); - return Convert.ToInt64(enclosureElement.Attribute("length").Value); - } - return 0; - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Fanzub/FanzubRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Fanzub/FanzubRequestGenerator.cs new file mode 100644 index 000000000..bd54393ff --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Fanzub/FanzubRequestGenerator.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using NzbDrone.Common; +using NzbDrone.Common.Http; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.Indexers.Fanzub +{ + public class FanzubRequestGenerator : IIndexerRequestGenerator + { + private static readonly Regex RemoveCharactersRegex = new Regex(@"[!?`]", RegexOptions.Compiled); + + public String BaseUrl { get; set; } + public Int32 PageSize { get; set; } + + public FanzubRequestGenerator() + { + BaseUrl = "http://fanzub.com/rss/?cat=anime"; + PageSize = 100; + } + + public virtual IList> GetRecentRequests() + { + var pageableRequests = new List>(); + + pageableRequests.AddIfNotNull(GetPagedRequests(null)); + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(SeasonSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + var searchTitles = searchCriteria.QueryTitles.SelectMany(v => GetTitleSearchStrings(v, searchCriteria.AbsoluteEpisodeNumber)).ToList(); + + pageableRequests.Add(GetPagedRequests(String.Join("|", searchTitles))); + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + + private IEnumerable GetPagedRequests(String query) + { + var url = new StringBuilder(); + url.AppendFormat("{0}&max={1}", BaseUrl, PageSize); + + if (query.IsNotNullOrWhiteSpace()) + { + url.AppendFormat("&q={0}", query); + } + + yield return new IndexerRequest(url.ToString(), HttpAccept.Rss); + } + + private IEnumerable GetTitleSearchStrings(string title, int absoluteEpisodeNumber) + { + var formats = new[] { "{0}%20{1:00}", "{0}%20-%20{1:00}" }; + + return formats.Select(s => "\"" + String.Format(s, CleanTitle(title), absoluteEpisodeNumber) + "\""); + } + + private String CleanTitle(String title) + { + return RemoveCharactersRegex.Replace(title, ""); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/FetchAndParseRssService.cs b/src/NzbDrone.Core/Indexers/FetchAndParseRssService.cs index 69e498783..4e9dd1ccc 100644 --- a/src/NzbDrone.Core/Indexers/FetchAndParseRssService.cs +++ b/src/NzbDrone.Core/Indexers/FetchAndParseRssService.cs @@ -15,13 +15,11 @@ public interface IFetchAndParseRss public class FetchAndParseRssService : IFetchAndParseRss { private readonly IIndexerFactory _indexerFactory; - private readonly IFetchFeedFromIndexers _feedFetcher; private readonly Logger _logger; - public FetchAndParseRssService(IIndexerFactory indexerFactory, IFetchFeedFromIndexers feedFetcher, Logger logger) + public FetchAndParseRssService(IIndexerFactory indexerFactory, Logger logger) { _indexerFactory = indexerFactory; - _feedFetcher = feedFetcher; _logger = logger; } @@ -48,7 +46,7 @@ public List Fetch() var task = taskFactory.StartNew(() => { - var indexerFeed = _feedFetcher.FetchRss(indexerLocal); + var indexerFeed = indexerLocal.FetchRecent(); lock (result) { diff --git a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs new file mode 100644 index 000000000..6f57b8132 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.Indexers +{ + public abstract class HttpIndexerBase : IndexerBase + where TSettings : IProviderConfig, new() + { + private const Int32 MaxNumResultsPerQuery = 1000; + + private readonly IHttpClient _httpClient; + + public override bool SupportsRss { get { return true; } } + public override bool SupportsSearch { get { return true; } } + public bool SupportsPaging { get { return PageSize > 0; } } + + public virtual Int32 PageSize { get { return 0; } } + + public abstract IIndexerRequestGenerator GetRequestGenerator(); + public abstract IParseIndexerResponse GetParser(); + + public HttpIndexerBase(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(configService, parsingService, logger) + { + _httpClient = httpClient; + } + + public override IList FetchRecent() + { + if (!SupportsRss) + { + return new List(); + } + + var generator = GetRequestGenerator(); + + return FetchReleases(generator.GetRecentRequests()); + } + + public override IList Fetch(SingleEpisodeSearchCriteria searchCriteria) + { + if (!SupportsSearch) + { + return new List(); + } + + var generator = GetRequestGenerator(); + + return FetchReleases(generator.GetSearchRequests(searchCriteria)); + } + + public override IList Fetch(SeasonSearchCriteria searchCriteria) + { + if (!SupportsSearch) + { + return new List(); + } + + var generator = GetRequestGenerator(); + + return FetchReleases(generator.GetSearchRequests(searchCriteria)); + } + + public override IList Fetch(DailyEpisodeSearchCriteria searchCriteria) + { + if (!SupportsSearch) + { + return new List(); + } + + var generator = GetRequestGenerator(); + + return FetchReleases(generator.GetSearchRequests(searchCriteria)); + } + + public override IList Fetch(AnimeEpisodeSearchCriteria searchCriteria) + { + if (!SupportsSearch) + { + return new List(); + } + + var generator = GetRequestGenerator(); + + return FetchReleases(generator.GetSearchRequests(searchCriteria)); + } + + public override IList Fetch(SpecialEpisodeSearchCriteria searchCriteria) + { + if (!SupportsSearch) + { + return new List(); + } + + var generator = GetRequestGenerator(); + + return FetchReleases(generator.GetSearchRequests(searchCriteria)); + } + + protected virtual IList FetchReleases(IList> pageableRequests) + { + var releases = new List(); + var url = String.Empty; + + var parser = GetParser(); + + try + { + foreach (var pageableRequest in pageableRequests) + { + var pagedReleases = new List(); + + foreach (var request in pageableRequest) + { + url = request.Url.ToString(); + + var page = FetchPage(request, parser); + + pagedReleases.AddRange(page); + + if (!IsFullPage(page) || pagedReleases.Count >= MaxNumResultsPerQuery) + { + break; + } + } + + releases.AddRange(pagedReleases); + } + } + catch (WebException webException) + { + if (webException.Message.Contains("502") || webException.Message.Contains("503") || + webException.Message.Contains("timed out")) + { + _logger.Warn("{0} server is currently unavailable. {1} {2}", this, url, webException.Message); + } + else + { + _logger.Warn("{0} {1} {2}", this, url, webException.Message); + } + } + catch (RequestLimitReachedException) + { + // TODO: Backoff for x period. + _logger.Warn("API Request Limit reached for {0}", this); + } + catch (ApiKeyException) + { + _logger.Warn("Invalid API Key for {0} {1}", this, url); + } + catch (Exception feedEx) + { + feedEx.Data.Add("FeedUrl", url); + _logger.ErrorException("An error occurred while processing feed. " + url, feedEx); + } + + return CleanupReleases(releases); + } + + protected virtual Boolean IsFullPage(IList page) + { + return PageSize != 0 && page.Count >= PageSize; + } + + protected virtual IList FetchPage(IndexerRequest request, IParseIndexerResponse parser) + { + var url = request.Url; + + _logger.Debug("Downloading Feed " + request.Url); + var response = new IndexerResponse(request, _httpClient.Execute(request.HttpRequest)); + + if (response.HttpResponse.Headers.ContentType != null && response.HttpResponse.Headers.ContentType.Contains("text/html") && + request.HttpRequest.Headers.Accept != null && !request.HttpRequest.Headers.Accept.Contains("text/html")) + { + throw new WebException("Indexer responded with html content. Site is likely blocked or unavailable."); + } + + return parser.ParseResponse(response).ToList(); + } + + protected override void Test(List failures) + { + failures.AddIfNotNull(TestConnection()); + } + + protected virtual ValidationFailure TestConnection() + { + // TODO: This doesn't even work coz those exceptions get catched. + try + { + var releases = FetchRecent(); + + if (releases.Any()) return null; + } + catch (ApiKeyException) + { + _logger.Warn("Indexer returned result for RSS URL, API Key appears to be invalid"); + + return new ValidationFailure("ApiKey", "Invalid API Key"); + } + catch (RequestLimitReachedException) + { + _logger.Warn("Request limit reached"); + } + catch (Exception ex) + { + _logger.WarnException("Unable to connect to indexer: " + ex.Message, ex); + + return new ValidationFailure("Url", "Unable to connect to indexer, check the log for more details"); + } + + return null; + } + } + +} diff --git a/src/NzbDrone.Core/Indexers/IIndexer.cs b/src/NzbDrone.Core/Indexers/IIndexer.cs index da67c45d1..93aefa7cf 100644 --- a/src/NzbDrone.Core/Indexers/IIndexer.cs +++ b/src/NzbDrone.Core/Indexers/IIndexer.cs @@ -1,23 +1,22 @@ using System; using System.Collections.Generic; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers { public interface IIndexer : IProvider { - IParseFeed Parser { get; } - DownloadProtocol Protocol { get; } - Int32 SupportedPageSize { get; } - Boolean SupportsPaging { get; } Boolean SupportsRss { get; } Boolean SupportsSearch { get; } - - IEnumerable RecentFeed { get; } - IEnumerable GetEpisodeSearchUrls(List titles, int tvRageId, int seasonNumber, int episodeNumber); - IEnumerable GetDailyEpisodeSearchUrls(List titles, int tvRageId, DateTime date); - IEnumerable GetAnimeEpisodeSearchUrls(List titles, int tvRageId, int absoluteEpisodeNumber); - IEnumerable GetSeasonSearchUrls(List titles, int tvRageId, int seasonNumber, int offset); - IEnumerable GetSearchUrls(string query, int offset = 0); + DownloadProtocol Protocol { get; } + + IList FetchRecent(); + IList Fetch(SeasonSearchCriteria searchCriteria); + IList Fetch(SingleEpisodeSearchCriteria searchCriteria); + IList Fetch(DailyEpisodeSearchCriteria searchCriteria); + IList Fetch(AnimeEpisodeSearchCriteria searchCriteria); + IList Fetch(SpecialEpisodeSearchCriteria searchCriteria); } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/IIndexerRequestGenerator.cs b/src/NzbDrone.Core/Indexers/IIndexerRequestGenerator.cs new file mode 100644 index 000000000..4a63d94c6 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/IIndexerRequestGenerator.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.Indexers +{ + public interface IIndexerRequestGenerator + { + IList> GetRecentRequests(); + IList> GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria); + IList> GetSearchRequests(SeasonSearchCriteria searchCriteria); + IList> GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria); + IList> GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria); + IList> GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria); + } +} diff --git a/src/NzbDrone.Core/Indexers/IParseFeed.cs b/src/NzbDrone.Core/Indexers/IParseFeed.cs deleted file mode 100644 index 8410d5e04..000000000 --- a/src/NzbDrone.Core/Indexers/IParseFeed.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.Indexers -{ - public interface IParseFeed - { - IEnumerable Process(string xml, string url); - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/IProcessIndexerResponse.cs b/src/NzbDrone.Core/Indexers/IProcessIndexerResponse.cs new file mode 100644 index 000000000..f687f6383 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/IProcessIndexerResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Indexers +{ + public interface IParseIndexerResponse + { + IList ParseResponse(IndexerResponse indexerResponse); + } +} diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs index 2dfc0d7d1..02e4b594a 100644 --- a/src/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs @@ -1,18 +1,40 @@ -using System; +using System; using System.Collections.Generic; +using System.Linq; +using System.Text; using FluentValidation.Results; +using NLog; +using NzbDrone.Common; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers { - public abstract class IndexerBase : IIndexer where TSettings : IProviderConfig, new() + public abstract class IndexerBase : IIndexer + where TSettings : IProviderConfig, new() { + protected readonly IConfigService _configService; + protected readonly IParsingService _parsingService; + protected readonly Logger _logger; + + public abstract DownloadProtocol Protocol { get; } + + public abstract Boolean SupportsRss { get; } + public abstract Boolean SupportsSearch { get; } + + public IndexerBase(IConfigService configService, IParsingService parsingService, Logger logger) + { + _configService = configService; + _parsingService = parsingService; + _logger = logger; + } + public Type ConfigContract { - get - { - return typeof(TSettings); - } + get { return typeof(TSettings); } } public virtual IEnumerable DefaultDefinitions @@ -24,7 +46,7 @@ public virtual IEnumerable DefaultDefinitions yield return new IndexerDefinition { Name = GetType().Name, - EnableRss = config.Validate().IsValid, + EnableRss = config.Validate().IsValid && SupportsRss, EnableSearch = config.Validate().IsValid && SupportsSearch, Implementation = GetType().Name, Settings = config @@ -34,14 +56,6 @@ public virtual IEnumerable DefaultDefinitions public virtual ProviderDefinition Definition { get; set; } - public abstract ValidationResult Test(); - public abstract DownloadProtocol Protocol { get; } - - public virtual Boolean SupportsRss { get { return true; } } - public virtual Boolean SupportsSearch { get { return true; } } - public virtual Int32 SupportedPageSize { get { return 0; } } - public bool SupportsPaging { get { return SupportedPageSize > 0; } } - protected TSettings Settings { get @@ -50,18 +64,48 @@ protected TSettings Settings } } - public virtual IParseFeed Parser { get; private set; } - - public abstract IEnumerable RecentFeed { get; } - public abstract IEnumerable GetEpisodeSearchUrls(List titles, int tvRageId, int seasonNumber, int episodeNumber); - public abstract IEnumerable GetDailyEpisodeSearchUrls(List titles, int tvRageId, DateTime date); - public abstract IEnumerable GetAnimeEpisodeSearchUrls(List titles, int tvRageId, int absoluteEpisodeNumber); - public abstract IEnumerable GetSeasonSearchUrls(List titles, int tvRageId, int seasonNumber, int offset); - public abstract IEnumerable GetSearchUrls(string query, int offset); + public abstract IList FetchRecent(); + public abstract IList Fetch(SeasonSearchCriteria searchCriteria); + public abstract IList Fetch(SingleEpisodeSearchCriteria searchCriteria); + public abstract IList Fetch(DailyEpisodeSearchCriteria searchCriteria); + public abstract IList Fetch(AnimeEpisodeSearchCriteria searchCriteria); + public abstract IList Fetch(SpecialEpisodeSearchCriteria searchCriteria); + + protected virtual IList CleanupReleases(IEnumerable releases) + { + var result = releases.DistinctBy(v => v.Guid).ToList(); + + result.ForEach(c => + { + c.Indexer = Definition.Name; + c.DownloadProtocol = Protocol; + }); + + return result; + } + + public ValidationResult Test() + { + var failures = new List(); + + try + { + Test(failures); + } + catch (Exception ex) + { + _logger.ErrorException("Test aborted due to exception", ex); + failures.Add(new ValidationFailure(string.Empty, "Test was aborted due to an error: " + ex.Message)); + } + + return new ValidationResult(failures); + } + + protected abstract void Test(List failures); public override string ToString() { return Definition.Name; } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/Indexers/IndexerFetchService.cs b/src/NzbDrone.Core/Indexers/IndexerFetchService.cs deleted file mode 100644 index 2fd7bb9ae..000000000 --- a/src/NzbDrone.Core/Indexers/IndexerFetchService.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using NLog; -using NzbDrone.Common; -using NzbDrone.Common.Http; -using NzbDrone.Core.Indexers.Exceptions; -using NzbDrone.Core.IndexerSearch.Definitions; -using NzbDrone.Core.Parser.Model; -using System.Linq; - -namespace NzbDrone.Core.Indexers -{ - public interface IFetchFeedFromIndexers - { - IList FetchRss(IIndexer indexer); - IList Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria); - IList Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria); - IList Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria); - IList Fetch(IIndexer indexer, AnimeEpisodeSearchCriteria searchCriteria); - IList Fetch(IIndexer indexer, SpecialEpisodeSearchCriteria searchCriteria); - } - - public class FetchFeedService : IFetchFeedFromIndexers - { - private readonly Logger _logger; - private readonly IHttpClient _httpClient; - - public FetchFeedService(IHttpClient httpClient, Logger logger) - { - _httpClient = httpClient; - _logger = logger; - } - - public virtual IList FetchRss(IIndexer indexer) - { - _logger.Debug("Fetching feeds from " + indexer); - - var result = Fetch(indexer, indexer.RecentFeed); - - _logger.Debug("Finished processing feeds from {0} found {1} releases", indexer, result.Count); - - return result; - } - - public IList Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria) - { - return Fetch(indexer, searchCriteria, 0).DistinctBy(c => c.DownloadUrl).ToList(); - } - - private IList Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria, int offset) - { - var searchUrls = indexer.GetSeasonSearchUrls(searchCriteria.QueryTitles, searchCriteria.Series.TvRageId, searchCriteria.SeasonNumber, offset).ToList(); - - if (searchUrls.Any()) - { - _logger.Debug("Searching for {0} offset: {1}", searchCriteria, offset); - - var result = Fetch(indexer, searchUrls); - - _logger.Info("{0} offset {1}. Found {2}", indexer, offset, result.Count); - - if (indexer.SupportsPaging && result.Count >= indexer.SupportedPageSize && offset < 900) - { - result.AddRange(Fetch(indexer, searchCriteria, offset + indexer.SupportedPageSize)); - } - - //Only log finish for the first call to this recursive method - if (offset == 0) - { - _logger.Info("Finished searching {0} for {1}. Found {2}", indexer, searchCriteria, result.Count); - } - - return result; - } - - return new List(); - } - - public IList Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria) - { - var searchUrls = indexer.GetEpisodeSearchUrls(searchCriteria.QueryTitles, searchCriteria.Series.TvRageId, searchCriteria.SeasonNumber, searchCriteria.EpisodeNumber).ToList(); - return Fetch(indexer, searchUrls, searchCriteria); - } - - public IList Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria) - { - var searchUrls = indexer.GetDailyEpisodeSearchUrls(searchCriteria.QueryTitles, searchCriteria.Series.TvRageId, searchCriteria.AirDate).ToList(); - return Fetch(indexer, searchUrls, searchCriteria); - } - - public IList Fetch(IIndexer indexer, AnimeEpisodeSearchCriteria searchCriteria) - { - var searchUrls = indexer.GetAnimeEpisodeSearchUrls(searchCriteria.SceneTitles, searchCriteria.Series.TvRageId, searchCriteria.AbsoluteEpisodeNumber).ToList(); - return Fetch(indexer, searchUrls, searchCriteria); - } - - public IList Fetch(IIndexer indexer, SpecialEpisodeSearchCriteria searchCriteria) - { - var searchUrls = new List(); - - foreach (var episodeQueryTitle in searchCriteria.EpisodeQueryTitles) - { - var urls = indexer.GetSearchUrls(episodeQueryTitle).ToList(); - - if (urls.Any()) - { - _logger.Debug("Performing query of {0} for {1}", indexer, episodeQueryTitle); - searchUrls.AddRange(urls); - } - } - - return Fetch(indexer, searchUrls, searchCriteria); - } - - private List Fetch(IIndexer indexer, IEnumerable urls, SearchCriteriaBase searchCriteria) - { - var urlList = urls.ToList(); - - if (urlList.Empty()) - { - return new List(); - } - - _logger.Debug("Searching for {0}", searchCriteria); - - var result = Fetch(indexer, urlList); - - _logger.Info("Finished searching {0} for {1}. Found {2}", indexer, searchCriteria, result.Count); - - return result; - } - - private List Fetch(IIndexer indexer, IEnumerable urls) - { - var result = new List(); - - foreach (var url in urls) - { - try - { - _logger.Debug("Downloading Feed " + url); - var request = new HttpRequest(url); - request.Headers.Accept = "text/xml, text/rss+xml, application/rss+xml"; - var response = _httpClient.Get(request); - - if (response.Headers.ContentType != null && response.Headers.ContentType.Split(';')[0] == "text/html") - { - throw new WebException("Indexer responded with html content. Site is likely blocked or unavailable."); - } - - var xml = response.Content; - if (!string.IsNullOrWhiteSpace(xml)) - { - result.AddRange(indexer.Parser.Process(xml, url)); - } - else - { - _logger.Warn("{0} returned empty response.", url); - } - - } - catch (WebException webException) - { - if (webException.Message.Contains("502") || webException.Message.Contains("503") || - webException.Message.Contains("timed out")) - { - _logger.Warn("{0} server is currently unavailable. {1} {2}", indexer, url, webException.Message); - } - else - { - _logger.Warn("{0} {1} {2}", indexer, url, webException.Message); - } - } - catch (ApiKeyException) - { - _logger.Warn("Invalid API Key for {0} {1}", indexer, url); - } - catch (Exception feedEx) - { - feedEx.Data.Add("FeedUrl", url); - _logger.ErrorException("An error occurred while processing feed. " + url, feedEx); - } - } - - result = result.DistinctBy(v => v.Guid).ToList(); - - result.ForEach(c => - { - c.Indexer = indexer.Definition.Name; - c.DownloadProtocol = indexer.Protocol; - }); - - return result; - } - } -} diff --git a/src/NzbDrone.Core/Indexers/IndexerRequest.cs b/src/NzbDrone.Core/Indexers/IndexerRequest.cs new file mode 100644 index 000000000..d20c920dc --- /dev/null +++ b/src/NzbDrone.Core/Indexers/IndexerRequest.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Common.Http; + +namespace NzbDrone.Core.Indexers +{ + public class IndexerRequest + { + public HttpRequest HttpRequest { get; private set; } + + public IndexerRequest(String url, HttpAccept httpAccept) + { + HttpRequest = new HttpRequest(url, httpAccept); + } + + public IndexerRequest(HttpRequest httpRequest) + { + HttpRequest = httpRequest; + } + + public Uri Url + { + get { return HttpRequest.Url; } + } + } + + public class IndexerResponse + { + private readonly IndexerRequest _indexerRequest; + private readonly HttpResponse _httpResponse; + + public IndexerResponse(IndexerRequest indexerRequest, HttpResponse httpResponse) + { + _indexerRequest = indexerRequest; + _httpResponse = httpResponse; + } + + public IndexerRequest Request + { + get { return _indexerRequest; } + } + + public HttpRequest HttpRequest + { + get { return _httpResponse.Request; } + } + + public HttpResponse HttpResponse + { + get { return _httpResponse; } + } + + public String Content + { + get { return _httpResponse.Content; } + } + } +} diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs index f2797d695..650c2b1d2 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -5,185 +5,49 @@ using NLog; using NzbDrone.Common; using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.Parser; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers.Newznab { - public class Newznab : IndexerBase + public class Newznab : HttpIndexerBase { - private readonly IFetchFeedFromIndexers _feedFetcher; - private readonly HttpProvider _httpProvider; - private readonly Logger _logger; - - public Newznab(IFetchFeedFromIndexers feedFetcher, HttpProvider httpProvider, Logger logger) - { - _feedFetcher = feedFetcher; - _httpProvider = httpProvider; - _logger = logger; - } - - //protected so it can be mocked, but not used for DI - //TODO: Is there a better way to achieve this? - protected Newznab() - { - } - public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } } - public override Int32 SupportedPageSize { get { return 100; } } + public override Int32 PageSize { get { return 100; } } - public override IParseFeed Parser + public override IIndexerRequestGenerator GetRequestGenerator() { - get + return new NewznabRequestGenerator() { - return new NewznabParser(); - } + PageSize = PageSize, + Settings = Settings + }; + } + + public override IParseIndexerResponse GetParser() + { + return new NewznabRssParser(); } public override IEnumerable DefaultDefinitions { get { - var list = new List(); - - list.Add(GetDefinition("Nzbs.org", GetSettings("http://nzbs.org", 5000))); - list.Add(GetDefinition("Nzb.su", GetSettings("https://api.nzb.su"))); - list.Add(GetDefinition("Dognzb.cr", GetSettings("https://api.dognzb.cr"))); - list.Add(GetDefinition("OZnzb.com", GetSettings("https://www.oznzb.com"))); - list.Add(GetDefinition("nzbplanet.net", GetSettings("https://nzbplanet.net"))); - list.Add(GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info"))); - - return list; + yield return GetDefinition("Nzbs.org", GetSettings("http://nzbs.org", 5000)); + yield return GetDefinition("Nzb.su", GetSettings("https://api.nzb.su")); + yield return GetDefinition("Dognzb.cr", GetSettings("https://api.dognzb.cr")); + yield return GetDefinition("OZnzb.com", GetSettings("https://www.oznzb.com")); + yield return GetDefinition("nzbplanet.net", GetSettings("https://nzbplanet.net")); + yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info")); } } - public override ProviderDefinition Definition { get; set; } - - public override IEnumerable RecentFeed + public Newznab(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, configService, parsingService, logger) { - get - { - var categories = String.Join(",", Settings.Categories.Concat(Settings.AnimeCategories)); - var url = String.Format("{0}/api?t=tvsearch&cat={1}&extended=1{2}", Settings.Url.TrimEnd('/'), categories, Settings.AdditionalParameters); - - if (!String.IsNullOrWhiteSpace(Settings.ApiKey)) - { - url += "&apikey=" + Settings.ApiKey; - } - - yield return url; - } - } - - public override IEnumerable GetEpisodeSearchUrls(List titles, int tvRageId, int seasonNumber, int episodeNumber) - { - if (Settings.Categories.Empty()) - { - return Enumerable.Empty(); - } - - if (tvRageId > 0) - { - return RecentFeed.Select(url => String.Format("{0}&limit=100&rid={1}&season={2}&ep={3}", url, tvRageId, seasonNumber, episodeNumber)); - } - - return titles.SelectMany(title => - RecentFeed.Select(url => - String.Format("{0}&limit=100&q={1}&season={2}&ep={3}", - url, NewsnabifyTitle(title), seasonNumber, episodeNumber))); - } - - public override IEnumerable GetDailyEpisodeSearchUrls(List titles, int tvRageId, DateTime date) - { - if (Settings.Categories.Empty()) - { - return Enumerable.Empty(); - } - - if (tvRageId > 0) - { - return RecentFeed.Select(url => String.Format("{0}&limit=100&rid={1}&season={2:yyyy}&ep={2:MM}/{2:dd}", url, tvRageId, date)).ToList(); - } - - return titles.SelectMany(title => - RecentFeed.Select(url => - String.Format("{0}&limit=100&q={1}&season={2:yyyy}&ep={2:MM}/{2:dd}", - url, NewsnabifyTitle(title), date)).ToList()); - } - - public override IEnumerable GetAnimeEpisodeSearchUrls(List titles, int tvRageId, int absoluteEpisodeNumber) - { - if (Settings.AnimeCategories.Empty()) - { - return Enumerable.Empty(); - } - - return titles.SelectMany(title => - RecentFeed.Select(url => - String.Format("{0}&limit=100&q={1}+{2:00}", - url.Replace("t=tvsearch", "t=search"), NewsnabifyTitle(title), absoluteEpisodeNumber))); - } - - public override IEnumerable GetSeasonSearchUrls(List titles, int tvRageId, int seasonNumber, int offset) - { - if (Settings.Categories.Empty()) - { - return Enumerable.Empty(); - } - - if (tvRageId > 0) - { - return RecentFeed.Select(url => String.Format("{0}&limit=100&rid={1}&season={2}&offset={3}", url, tvRageId, seasonNumber, offset)); - } - - return titles.SelectMany(title => - RecentFeed.Select(url => - String.Format("{0}&limit=100&q={1}&season={2}&offset={3}", - url, NewsnabifyTitle(title), seasonNumber, offset))); - } - - public override IEnumerable GetSearchUrls(string query, int offset) - { - // encode query (replace the + with spaces first) - query = query.Replace("+", " "); - query = System.Web.HttpUtility.UrlEncode(query); - return RecentFeed.Select(url => String.Format("{0}&offset={1}&limit=100&q={2}", url.Replace("t=tvsearch", "t=search"), offset, query)); - } - - public override ValidationResult Test() - { - var releases = _feedFetcher.FetchRss(this); - - if (releases.Any()) return new ValidationResult(); - - try - { - var url = RecentFeed.First(); - var xml = _httpProvider.DownloadString(url); - - NewznabPreProcessor.Process(xml, url); - } - catch (ApiKeyException) - { - _logger.Warn("Indexer returned result for Newznab RSS URL, API Key appears to be invalid"); - - var apiKeyFailure = new ValidationFailure("ApiKey", "Invalid API Key"); - return new ValidationResult(new List { apiKeyFailure }); - } - catch (RequestLimitReachedException) - { - _logger.Warn("Request limit reached"); - } - catch (Exception ex) - { - _logger.WarnException("Unable to connect to indexer: " + ex.Message, ex); - - var failure = new ValidationFailure("Url", "Unable to connect to indexer, check the log for more details"); - return new ValidationResult(new List { failure }); - } - - return new ValidationResult(); } private IndexerDefinition GetDefinition(String name, NewznabSettings settings) @@ -212,10 +76,5 @@ private NewznabSettings GetSettings(String url, params int[] categories) return settings; } - - private static string NewsnabifyTitle(string title) - { - return title.Replace("+", "%20"); - } } } diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs deleted file mode 100644 index 6dae6d50a..000000000 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabParser.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; -using NzbDrone.Core.Parser.Model; -using System.Globalization; - -namespace NzbDrone.Core.Indexers.Newznab -{ - public class NewznabParser : RssParserBase - { - - private static readonly string[] IgnoredErrors = - { - "Request limit reached", - }; - - protected override string GetNzbInfoUrl(XElement item) - { - return item.Comments().Replace("#comments", ""); - } - - protected override DateTime GetPublishDate(XElement item) - { - var attributes = item.Elements("attr").ToList(); - var usenetdateElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("usenetdate", StringComparison.CurrentCultureIgnoreCase)); - - if (usenetdateElement != null) - { - var dateString = usenetdateElement.Attribute("value").Value; - - return XElementExtensions.ParseDate(dateString); - } - - return base.GetPublishDate(item); - } - - protected override long GetSize(XElement item) - { - var attributes = item.Elements("attr").ToList(); - var sizeElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("size", StringComparison.CurrentCultureIgnoreCase)); - - if (sizeElement != null) - { - return Convert.ToInt64(sizeElement.Attribute("value").Value); - } - - return ParseSize(item.Description(), true); - } - - public override IEnumerable Process(string xml, string url) - { - try - { - return base.Process(xml, url); - } - catch (NewznabException e) - { - if (!IgnoredErrors.Any(ignoredError => e.Message.Contains(ignoredError))) - { - throw; - } - _logger.Error(e.Message); - return new List(); - } - } - - protected override ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult) - { - if (currentResult != null) - { - var attributes = item.Elements("attr").ToList(); - - var rageIdElement = attributes.SingleOrDefault(e => e.Attribute("name").Value.Equals("rageid", StringComparison.CurrentCultureIgnoreCase)); - - if (rageIdElement != null) - { - int tvRageId; - - if (Int32.TryParse(rageIdElement.Attribute("value").Value, out tvRageId)) - { - currentResult.TvRageId = tvRageId; - } - } - } - - return currentResult; - } - - protected override void PreProcess(string source, string url) - { - NewznabPreProcessor.Process(source, url); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabPreProcessor.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabPreProcessor.cs deleted file mode 100644 index c64a9cecb..000000000 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabPreProcessor.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Linq; -using System.Xml.Linq; -using NzbDrone.Core.Indexers.Exceptions; - -namespace NzbDrone.Core.Indexers.Newznab -{ - public static class NewznabPreProcessor - { - public static void Process(string source, string url) - { - var xdoc = XDocument.Parse(source); - var error = xdoc.Descendants("error").FirstOrDefault(); - - if (error == null) return; - - var code = Convert.ToInt32(error.Attribute("code").Value); - var errorMessage = error.Attribute("description").Value; - - if (code >= 100 && code <= 199) throw new ApiKeyException("Invalid API key"); - - if (!url.Contains("apikey=") && errorMessage == "Missing parameter") - { - throw new ApiKeyException("Indexer requires an API key"); - } - - if (errorMessage == "Request limit reached") - { - throw new RequestLimitReachedException("API limit reached"); - } - - throw new NewznabException("Newznab error detected: {0}", errorMessage); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs new file mode 100644 index 000000000..c993999f0 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Common; +using NzbDrone.Common.Http; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.Indexers.Newznab +{ + public class NewznabRequestGenerator : IIndexerRequestGenerator + { + public Int32 MaxPages { get; set; } + public Int32 PageSize { get; set; } + public NewznabSettings Settings { get; set; } + + public NewznabRequestGenerator() + { + MaxPages = 30; + PageSize = 100; + } + + public virtual IList> GetRecentRequests() + { + var pageableRequests = new List>(); + + // TODO: We might consider getting multiple pages in the future, but atm we limit it to 1 page. + pageableRequests.AddIfNotNull(GetPagedRequests(1, Settings.Categories.Concat(Settings.AnimeCategories), "tvsearch", "")); + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + if (searchCriteria.Series.TvRageId > 0) + { + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", + String.Format("&rid={0}&season={1}&ep={2}", + searchCriteria.Series.TvRageId, + searchCriteria.SeasonNumber, + searchCriteria.EpisodeNumber))); + } + else + { + foreach (var queryTitle in searchCriteria.QueryTitles) + { + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", + String.Format("&q={0}&season={1}&ep={2}", + NewsnabifyTitle(queryTitle), + searchCriteria.SeasonNumber, + searchCriteria.EpisodeNumber))); + } + } + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(SeasonSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + if (searchCriteria.Series.TvRageId > 0) + { + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", + String.Format("&rid={0}&season={1}", + searchCriteria.Series.TvRageId, + searchCriteria.SeasonNumber))); + } + else + { + foreach (var queryTitle in searchCriteria.QueryTitles) + { + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", + String.Format("&q={0}&season={1}", + NewsnabifyTitle(queryTitle), + searchCriteria.SeasonNumber))); + } + } + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + if (searchCriteria.Series.TvRageId > 0) + { + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", + String.Format("&rid={0}&season={1:yyyy}&ep={1:MM}/{1:dd}", + searchCriteria.Series.TvRageId, + searchCriteria.AirDate))); + } + else + { + foreach (var queryTitle in searchCriteria.QueryTitles) + { + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", + String.Format("&q={0}&season={1:yyyy}&ep={1:MM}/{1:dd}", + NewsnabifyTitle(queryTitle), + searchCriteria.AirDate))); + } + } + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + foreach (var queryTitle in searchCriteria.QueryTitles) + { + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, Settings.AnimeCategories, "search", + String.Format("&q={0}+{1:00}", + NewsnabifyTitle(queryTitle), + searchCriteria.AbsoluteEpisodeNumber))); + } + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + foreach (var queryTitle in searchCriteria.EpisodeQueryTitles) + { + var query = queryTitle.Replace('+', ' '); + query = System.Web.HttpUtility.UrlEncode(query); + + pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, Settings.Categories.Concat(Settings.AnimeCategories), "search", + String.Format("&q={0}", + query))); + } + + return pageableRequests; + } + + private IEnumerable GetPagedRequests(Int32 maxPages, IEnumerable categories, String searchType, String parameters) + { + if (categories.Empty()) + { + yield break; + } + + var categoriesQuery = String.Join(",", categories.Distinct()); + + var baseUrl = String.Format("{0}/api?t={1}&cat={2}&extended=1{3}", Settings.Url.TrimEnd('/'), searchType, categoriesQuery, Settings.AdditionalParameters); + + if (Settings.ApiKey.IsNotNullOrWhiteSpace()) + { + baseUrl += "&apikey=" + Settings.ApiKey; + } + + if (PageSize == 0) + { + yield return new IndexerRequest(String.Format("{0}{1}", baseUrl, parameters), HttpAccept.Rss); + } + else + { + for (var page = 0; page < maxPages; page++) + { + yield return new IndexerRequest(String.Format("{0}&offset={1}&limit={2}{3}", baseUrl, page * PageSize, PageSize, parameters), HttpAccept.Rss); + } + } + } + + private static String NewsnabifyTitle(String title) + { + return title.Replace("+", "%20"); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabRssParser.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabRssParser.cs new file mode 100644 index 000000000..bab522d32 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabRssParser.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using NzbDrone.Common; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Indexers.Newznab +{ + public class NewznabRssParser : RssParser + { + public const String ns = "{http://www.newznab.com/DTD/2010/feeds/attributes/}"; + + protected override bool PreProcess(IndexerResponse indexerResponse) + { + var xdoc = XDocument.Parse(indexerResponse.Content); + var error = xdoc.Descendants("error").FirstOrDefault(); + + if (error == null) return true; + + var code = Convert.ToInt32(error.Attribute("code").Value); + var errorMessage = error.Attribute("description").Value; + + if (code >= 100 && code <= 199) throw new ApiKeyException("Invalid API key"); + + if (!indexerResponse.Request.Url.ToString().Contains("apikey=") && errorMessage == "Missing parameter") + { + throw new ApiKeyException("Indexer requires an API key"); + } + + if (errorMessage == "Request limit reached") + { + throw new RequestLimitReachedException("API limit reached"); + } + + throw new NewznabException("Newznab error detected: {0}", errorMessage); + } + + protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo) + { + releaseInfo = base.ProcessItem(item, releaseInfo); + + releaseInfo.TvRageId = GetTvRageId(item); + + return releaseInfo; + } + + + protected override String GetInfoUrl(XElement item) + { + return item.TryGetValue("comments").TrimEnd("#comments"); + } + + protected override String GetCommentUrl(XElement item) + { + return item.TryGetValue("comments"); + } + + protected override Int64 GetSize(XElement item) + { + Int64 size; + + var sizeString = TryGetNewznabAttribute(item, "size"); + if (!sizeString.IsNullOrWhiteSpace() && Int64.TryParse(sizeString, out size)) + { + return size; + } + + size = GetEnclosureLength(item); + + return size; + } + + protected override DateTime GetPublishDate(XElement item) + { + var dateString = TryGetNewznabAttribute(item, "usenetdate"); + if (!dateString.IsNullOrWhiteSpace()) + { + return XElementExtensions.ParseDate(dateString); + } + + return base.GetPublishDate(item); + } + + protected virtual Int32 GetTvRageId(XElement item) + { + var tvRageIdString = TryGetNewznabAttribute(item, "rageid"); + Int32 tvRageId; + + if (!tvRageIdString.IsNullOrWhiteSpace() && Int32.TryParse(tvRageIdString, out tvRageId)) + { + return tvRageId; + } + + return 0; + } + + protected String TryGetNewznabAttribute(XElement item, String key, String defaultValue = "") + { + var attr = item.Elements(ns + "attr").SingleOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.CurrentCultureIgnoreCase)); + + if (attr != null) + { + return attr.Attribute("value").Value; + } + + return defaultValue; + } + } +} diff --git a/src/NzbDrone.Core/Indexers/NewznabTestService.cs b/src/NzbDrone.Core/Indexers/NewznabTestService.cs deleted file mode 100644 index bc923786e..000000000 --- a/src/NzbDrone.Core/Indexers/NewznabTestService.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using FluentValidation; -using FluentValidation.Results; -using NLog; -using NzbDrone.Common; -using NzbDrone.Common.Http; -using NzbDrone.Core.Indexers.Exceptions; -using NzbDrone.Core.Indexers.Newznab; - -namespace NzbDrone.Core.Indexers -{ - public interface INewznabTestService - { - void Test(IIndexer indexer); - } - - public class NewznabTestService : INewznabTestService - { - private readonly IFetchFeedFromIndexers _feedFetcher; - private readonly IHttpProvider _httpProvider; - private readonly Logger _logger; - - public NewznabTestService(IFetchFeedFromIndexers feedFetcher, IHttpProvider httpProvider, Logger logger) - { - _feedFetcher = feedFetcher; - _httpProvider = httpProvider; - _logger = logger; - } - - public void Test(IIndexer indexer) - { - var releases = _feedFetcher.FetchRss(indexer); - - if (releases.Any()) return; - - try - { - var url = indexer.RecentFeed.First(); - var xml = _httpProvider.DownloadString(url); - - NewznabPreProcessor.Process(xml, url); - } - catch (ApiKeyException) - { - _logger.Warn("Indexer returned result for Newznab RSS URL, API Key appears to be invalid"); - - var apiKeyFailure = new ValidationFailure("ApiKey", "Invalid API Key"); - throw new ValidationException(new List {apiKeyFailure}.ToArray()); - } - catch (RequestLimitReachedException) - { - _logger.Warn("Request limit reached"); - } - catch (Exception ex) - { - _logger.WarnException("Indexer doesn't appear to be Newznab based: " + ex.Message, ex); - - var failure = new ValidationFailure("Url", "Invalid Newznab URL, check log for details"); - throw new ValidationException(new List { failure }.ToArray()); - } - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs index 77161f28e..bc3e04cf7 100644 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs +++ b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs @@ -2,89 +2,31 @@ using System.Collections.Generic; using System.Linq; using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; namespace NzbDrone.Core.Indexers.Omgwtfnzbs { - public class Omgwtfnzbs : IndexerBase + public class Omgwtfnzbs : HttpIndexerBase { public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } } - public override IParseFeed Parser + public Omgwtfnzbs(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, configService, parsingService, logger) { - get - { - return new OmgwtfnzbsParser(); - } + } - public override IEnumerable RecentFeed + public override IIndexerRequestGenerator GetRequestGenerator() { - get - { - yield return String.Format("http://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user={0}&api={1}&eng=1", - Settings.Username, Settings.ApiKey); - } + return new OmgwtfnzbsRequestGenerator() { Settings = Settings }; } - public override IEnumerable GetEpisodeSearchUrls(List titles, int tvRageId, int seasonNumber, int episodeNumber) + public override IParseIndexerResponse GetParser() { - var searchUrls = new List(); - - foreach (var url in RecentFeed) - { - foreach (var title in titles) - { - searchUrls.Add(String.Format("{0}&search={1}+S{2:00}E{3:00}", url, title, seasonNumber, episodeNumber)); - } - } - - return searchUrls; - } - - public override IEnumerable GetDailyEpisodeSearchUrls(List titles, int tvRageId, DateTime date) - { - var searchUrls = new List(); - - foreach (var url in RecentFeed) - { - foreach (var title in titles) - { - searchUrls.Add(String.Format("{0}&search={1}+{2:yyyy MM dd}", url, title, date)); - } - } - - return searchUrls; - } - - public override IEnumerable GetAnimeEpisodeSearchUrls(List titles, int tvRageId, int absoluteEpisodeNumber) - { - // TODO: Implement - return new List(); - } - - public override IEnumerable GetSeasonSearchUrls(List titles, int tvRageId, int seasonNumber, int offset) - { - var searchUrls = new List(); - - foreach (var url in RecentFeed) - { - foreach (var title in titles) - { - searchUrls.Add(String.Format("{0}&search={1}+S{2:00}", url, title, seasonNumber)); - } - } - - return searchUrls; - } - - public override IEnumerable GetSearchUrls(string query, int offset) - { - return new List(); - } - - public override ValidationResult Test() - { - return new ValidationResult(); + return new OmgwtfnzbsRssParser(); } } } diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs new file mode 100644 index 000000000..24ca33843 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRequestGenerator.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Common; +using NzbDrone.Common.Http; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.Indexers.Omgwtfnzbs +{ + public class OmgwtfnzbsRequestGenerator : IIndexerRequestGenerator + { + public String BaseUrl { get; set; } + public OmgwtfnzbsSettings Settings { get; set; } + + public OmgwtfnzbsRequestGenerator() + { + BaseUrl = "http://rss.omgwtfnzbs.org/rss-search.php"; + } + + public virtual IList> GetRecentRequests() + { + var pageableRequests = new List>(); + + pageableRequests.AddIfNotNull(GetPagedRequests(null)); + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + foreach (var queryTitle in searchCriteria.QueryTitles) + { + pageableRequests.AddIfNotNull(GetPagedRequests(String.Format("{0}+S{1:00}E{2:00}", + queryTitle, + searchCriteria.SeasonNumber, + searchCriteria.EpisodeNumber))); + } + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(SeasonSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + foreach (var queryTitle in searchCriteria.QueryTitles) + { + pageableRequests.AddIfNotNull(GetPagedRequests(String.Format("{0}+S{1:00}", + queryTitle, + searchCriteria.SeasonNumber))); + } + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + foreach (var queryTitle in searchCriteria.QueryTitles) + { + pageableRequests.AddIfNotNull(GetPagedRequests(String.Format("{0}+{1:yyyy MM dd}", + queryTitle, + searchCriteria.AirDate))); + } + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria) + { + var pageableRequests = new List>(); + + foreach (var queryTitle in searchCriteria.EpisodeQueryTitles) + { + var query = queryTitle.Replace('+', ' '); + query = System.Web.HttpUtility.UrlEncode(query); + + pageableRequests.AddIfNotNull(GetPagedRequests(query)); + } + + return pageableRequests; + } + + private IEnumerable GetPagedRequests(String query) + { + var url = new StringBuilder(); + url.AppendFormat("{0}?catid=19,20&user={1}&api={2}&eng=1", BaseUrl, Settings.Username, Settings.ApiKey); + + if (query.IsNotNullOrWhiteSpace()) + { + url.AppendFormat("&search={0}", query); + } + + yield return new IndexerRequest(url.ToString(), HttpAccept.Rss); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRssParser.cs similarity index 60% rename from src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs rename to src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRssParser.cs index 95852ce67..96c0f90da 100644 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsParser.cs +++ b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsRssParser.cs @@ -4,9 +4,15 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs { - public class OmgwtfnzbsParser : RssParserBase + public class OmgwtfnzbsRssParser : RssParser { - protected override string GetNzbInfoUrl(XElement item) + public OmgwtfnzbsRssParser() + { + UseEnclosureUrl = true; + UseEnclosureLength = true; + } + + protected override string GetInfoUrl(XElement item) { //Todo: Me thinks I need to parse details to get this... var match = Regex.Match(item.Description(), @"(?:\View NZB\:\<\/b\>\s\.+)(?:\""\starget)", @@ -19,11 +25,5 @@ protected override string GetNzbInfoUrl(XElement item) return String.Empty; } - - protected override long GetSize(XElement item) - { - var sizeString = Regex.Match(item.Description(), @"(?:Size:\<\/b\>\s\d+\.)\d{1,2}\s\w{2}(?:\
)", RegexOptions.IgnoreCase | RegexOptions.Compiled).Value; - return ParseSize(sizeString, true); - } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/RssIndexerRequestGenerator.cs b/src/NzbDrone.Core/Indexers/RssIndexerRequestGenerator.cs new file mode 100644 index 000000000..ea540ebc0 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/RssIndexerRequestGenerator.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Common.Http; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.Indexers +{ + public class RssIndexerRequestGenerator : IIndexerRequestGenerator + { + private readonly String _baseUrl; + + public RssIndexerRequestGenerator(String baseUrl) + { + _baseUrl = baseUrl; + } + + + public virtual IList> GetRecentRequests() + { + var pageableRequests = new List>(); + + pageableRequests.Add(new[] { new IndexerRequest(_baseUrl, HttpAccept.Rss) }); + + return pageableRequests; + } + + public virtual IList> GetSearchRequests(SingleEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(SeasonSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(DailyEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(AnimeEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + + public virtual IList> GetSearchRequests(SpecialEpisodeSearchCriteria searchCriteria) + { + return new List>(); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/RssParser.cs b/src/NzbDrone.Core/Indexers/RssParser.cs new file mode 100644 index 000000000..094fa2a7f --- /dev/null +++ b/src/NzbDrone.Core/Indexers/RssParser.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Linq; +using NLog; +using NzbDrone.Common; +using NzbDrone.Common.Instrumentation; +using NzbDrone.Core.Indexers.Exceptions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Indexers +{ + public class RssParser : IParseIndexerResponse + { + protected readonly Logger _logger; + + // Use the 'guid' element content as InfoUrl. + public Boolean UseGuidInfoUrl { get; set; } + + // Use the enclosure as download url and/or length. + public Boolean UseEnclosureUrl { get; set; } + public Boolean UseEnclosureLength { get; set; } + + // Parse "Size: 1.3 GB" or "1.3 GB" parts in the description element and use that as Size. + public Boolean ParseSizeInDescription { get; set; } + + public RssParser() + { + _logger = NzbDroneLogger.GetLogger(this); + } + + public virtual IList ParseResponse(IndexerResponse indexerResponse) + { + var releases = new List(); + + if (!PreProcess(indexerResponse)) + { + return releases; + } + + using (var xmlTextReader = XmlReader.Create(new StringReader(indexerResponse.Content), new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true })) + { + var document = XDocument.Load(xmlTextReader); + var items = document.Root.Element("channel").Elements("item"); + + foreach (var item in items) + { + try + { + var reportInfo = ProcessItem(item); + + releases.AddIfNotNull(reportInfo); + } + catch (Exception itemEx) + { + itemEx.Data.Add("Item", item.Title()); + _logger.ErrorException("An error occurred while processing feed item from " + indexerResponse.Request.Url, itemEx); + } + } + } + + return releases; + } + + protected virtual ReleaseInfo CreateNewReleaseInfo() + { + return new ReleaseInfo(); + } + + protected virtual Boolean PreProcess(IndexerResponse indexerResponse) + { + if (indexerResponse.HttpResponse.StatusCode != System.Net.HttpStatusCode.OK) + { + throw new IndexerException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode); + } + + return true; + } + + protected ReleaseInfo ProcessItem(XElement item) + { + var releaseInfo = CreateNewReleaseInfo(); + + releaseInfo = ProcessItem(item, releaseInfo); + + _logger.Trace("Parsed: {0}", releaseInfo.Title); + + return PostProcess(item, releaseInfo); + } + + protected virtual ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo) + { + releaseInfo.Guid = GetGuid(item); + releaseInfo.Title = GetTitle(item); + releaseInfo.PublishDate = GetPublishDate(item); + releaseInfo.DownloadUrl = GetDownloadUrl(item); + releaseInfo.InfoUrl = GetInfoUrl(item); + releaseInfo.CommentUrl = GetCommentUrl(item); + + try + { + releaseInfo.Size = GetSize(item); + } + catch (Exception) + { + throw new SizeParsingException("Unable to parse size from: {0}", releaseInfo.Title); + } + + return releaseInfo; + } + + protected virtual ReleaseInfo PostProcess(XElement item, ReleaseInfo releaseInfo) + { + return releaseInfo; + } + + protected virtual String GetGuid(XElement item) + { + return item.TryGetValue("guid", Guid.NewGuid().ToString()); + } + + protected virtual String GetTitle(XElement item) + { + return item.TryGetValue("title", "Unknown"); + } + + protected virtual DateTime GetPublishDate(XElement item) + { + var dateString = item.TryGetValue("pubDate"); + + return XElementExtensions.ParseDate(dateString); + } + + protected virtual string GetDownloadUrl(XElement item) + { + if (UseEnclosureUrl) + { + return item.Element("enclosure").Attribute("url").Value; + } + else + { + return item.Element("link").Value; + } + } + + protected virtual string GetInfoUrl(XElement item) + { + if (UseGuidInfoUrl) + { + return (String)item.Element("guid"); + } + + return String.Empty; + } + + protected virtual string GetCommentUrl(XElement item) + { + return (String)item.Element("comments"); + } + + protected virtual long GetSize(XElement item) + { + if (UseEnclosureLength) + { + return GetEnclosureLength(item); + } + else if (ParseSizeInDescription) + { + return ParseSize(item.Element("description").Value, true); + } + + return 0; + } + + protected virtual long GetEnclosureLength(XElement item) + { + var enclosure = item.Element("enclosure"); + + if (enclosure != null) + { + return (long)enclosure.Attribute("length"); + } + + return 0; + } + + private static readonly Regex ParseSizeRegex = new Regex(@"(?\d+\.\d{1,2}|\d+\,\d+\.\d{1,2}|\d+)\W?(?[KMG]i?B)", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + public static Int64 ParseSize(String sizeString, Boolean defaultToBinaryPrefix) + { + var match = ParseSizeRegex.Matches(sizeString); + + if (match.Count != 0) + { + var value = Decimal.Parse(Regex.Replace(match[0].Groups["value"].Value, "\\,", ""), CultureInfo.InvariantCulture); + + var unit = match[0].Groups["unit"].Value.ToLower(); + + switch (unit) + { + case "kb": + return ConvertToBytes(Convert.ToDouble(value), 1, defaultToBinaryPrefix); + case "mb": + return ConvertToBytes(Convert.ToDouble(value), 2, defaultToBinaryPrefix); + case "gb": + return ConvertToBytes(Convert.ToDouble(value), 3, defaultToBinaryPrefix); + case "kib": + return ConvertToBytes(Convert.ToDouble(value), 1, true); + case "mib": + return ConvertToBytes(Convert.ToDouble(value), 2, true); + case "gib": + return ConvertToBytes(Convert.ToDouble(value), 3, true); + default: + return (Int64)value; + } + } + return 0; + } + + private static Int64 ConvertToBytes(Double value, Int32 power, Boolean binaryPrefix) + { + var prefix = binaryPrefix ? 1024 : 1000; + var multiplier = Math.Pow(prefix, power); + var result = value * multiplier; + + return Convert.ToInt64(result); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/RssParserBase.cs b/src/NzbDrone.Core/Indexers/RssParserBase.cs deleted file mode 100644 index 85c3ba557..000000000 --- a/src/NzbDrone.Core/Indexers/RssParserBase.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml; -using System.Xml.Linq; -using NLog; -using NzbDrone.Common.Instrumentation; -using NzbDrone.Core.Indexers.Newznab; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.Indexers -{ - public abstract class RssParserBase : IParseFeed - { - protected readonly Logger _logger; - - protected virtual ReleaseInfo CreateNewReleaseInfo() - { - return new ReleaseInfo(); - } - - protected RssParserBase() - { - _logger = NzbDroneLogger.GetLogger(this); - } - - public virtual IEnumerable Process(string xml, string url) - { - PreProcess(xml, url); - - using (var xmlTextReader = XmlReader.Create(new StringReader(xml), new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true })) - { - - var document = XDocument.Load(xmlTextReader); - var items = document.Descendants("item"); - - var result = new List(); - - foreach (var item in items) - { - try - { - var reportInfo = ParseFeedItem(item.StripNameSpace(), url); - - if (reportInfo != null) - { - result.Add(reportInfo); - } - } - catch (Exception itemEx) - { - itemEx.Data.Add("Item", item.Title()); - _logger.ErrorException("An error occurred while processing feed item from " + url, itemEx); - } - } - - return result; - } - } - - private ReleaseInfo ParseFeedItem(XElement item, string url) - { - var reportInfo = CreateNewReleaseInfo(); - - reportInfo.Guid = GetGuid(item); - reportInfo.Title = GetTitle(item); - reportInfo.PublishDate = GetPublishDate(item); - reportInfo.DownloadUrl = GetNzbUrl(item); - reportInfo.InfoUrl = GetNzbInfoUrl(item); - - try - { - reportInfo.Size = GetSize(item); - } - catch (Exception) - { - throw new SizeParsingException("Unable to parse size from: {0} [{1}]", reportInfo.Title, url); - } - - _logger.Trace("Parsed: {0}", reportInfo.Title); - - return PostProcessor(item, reportInfo); - } - - protected virtual String GetGuid(XElement item) - { - return item.TryGetValue("guid", Guid.NewGuid().ToString()); - } - - protected virtual string GetTitle(XElement item) - { - return item.Title(); - } - - protected virtual DateTime GetPublishDate(XElement item) - { - return item.PublishDate(); - } - - protected virtual string GetNzbUrl(XElement item) - { - return item.Links().First(); - } - - protected virtual string GetNzbInfoUrl(XElement item) - { - return String.Empty; - } - - protected abstract long GetSize(XElement item); - - protected virtual void PreProcess(string source, string url) - { - } - - protected virtual ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult) - { - return currentResult; - } - - private static readonly Regex ReportSizeRegex = new Regex(@"(?\d+\.\d{1,2}|\d+\,\d+\.\d{1,2}|\d+)\W?(?GB|MB|GiB|MiB)", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - - public static Int64 ParseSize(String sizeString, Boolean defaultToBinaryPrefix) - { - var match = ReportSizeRegex.Matches(sizeString); - - if (match.Count != 0) - { - var cultureInfo = new CultureInfo("en-US"); - var value = Decimal.Parse(Regex.Replace(match[0].Groups["value"].Value, "\\,", ""), cultureInfo); - - var unit = match[0].Groups["unit"].Value.ToLower(); - - switch (unit) - { - case "kb": - return ConvertToBytes(Convert.ToDouble(value), 1, defaultToBinaryPrefix); - case "mb": - return ConvertToBytes(Convert.ToDouble(value), 2, defaultToBinaryPrefix); - case "gb": - return ConvertToBytes(Convert.ToDouble(value), 3, defaultToBinaryPrefix); - case "kib": - return ConvertToBytes(Convert.ToDouble(value), 1, true); - case "mib": - return ConvertToBytes(Convert.ToDouble(value), 2, true); - case "gib": - return ConvertToBytes(Convert.ToDouble(value), 3, true); - default: - return (Int64)value; - } - } - return 0; - } - - private static Int64 ConvertToBytes(Double value, Int32 power, Boolean binaryPrefix) - { - var prefix = binaryPrefix ? 1024 : 1000; - var multiplier = Math.Pow(prefix, power); - var result = value * multiplier; - - return Convert.ToInt64(result); - } - } -} diff --git a/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs b/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs index ebf1d123b..f688bcae2 100644 --- a/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs +++ b/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs @@ -1,56 +1,34 @@ using System; using System.Collections.Generic; +using System.Linq; using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Parser; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers.Wombles { - public class Wombles : IndexerBase + public class Wombles : HttpIndexerBase { public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } } public override bool SupportsSearch { get { return false; } } - public override IParseFeed Parser + public override IParseIndexerResponse GetParser() { - get - { - return new WomblesParser(); - } + return new WomblesRssParser(); } - public override IEnumerable RecentFeed + public override IIndexerRequestGenerator GetRequestGenerator() { - get { yield return "http://newshost.co.za/rss/?sec=TV&fr=false"; } + return new RssIndexerRequestGenerator("http://newshost.co.za/rss/?sec=TV&fr=false"); } - public override IEnumerable GetEpisodeSearchUrls(List titles, int tvRageId, int seasonNumber, int episodeNumber) + public Wombles(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) + : base(httpClient, configService, parsingService, logger) { - return new List(); - } - public override IEnumerable GetSeasonSearchUrls(List titles, int tvRageId, int seasonNumber, int offset) - { - return new List(); - } - - public override IEnumerable GetDailyEpisodeSearchUrls(List titles, int tvRageId, DateTime date) - { - return new List(); - } - - public override IEnumerable GetAnimeEpisodeSearchUrls(List titles, int tvRageId, int absoluteEpisodeNumber) - { - return new string[0]; - } - - public override IEnumerable GetSearchUrls(string query, int offset) - { - return new List(); - } - - public override ValidationResult Test() - { - return new ValidationResult(); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs b/src/NzbDrone.Core/Indexers/Wombles/WomblesRssParser.cs similarity index 72% rename from src/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs rename to src/NzbDrone.Core/Indexers/Wombles/WomblesRssParser.cs index 2372cac5c..ac260508b 100644 --- a/src/NzbDrone.Core/Indexers/Wombles/WomblesParser.cs +++ b/src/NzbDrone.Core/Indexers/Wombles/WomblesRssParser.cs @@ -3,15 +3,11 @@ namespace NzbDrone.Core.Indexers.Wombles { - public class WomblesParser : RssParserBase + public class WomblesRssParser : RssParser { - protected override string GetNzbInfoUrl(XElement item) - { - return null; - } - protected override long GetSize(XElement item) { + // TODO: this can be found in the description element. return 0; } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 459a5b9f8..8f1976560 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -384,42 +384,43 @@ - - + + - + + Code - - Code - + - + - - + + - - + + - + - + + + - +