From 6c86f1dfddb99dd07a90090594d16ba349875e65 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 13 Nov 2011 12:51:15 -0800 Subject: [PATCH] Newznab providers can be configured by the end user. --- NzbDrone.Core/CentralDispatch.cs | 1 + .../Datastore/Migrations/Migration20111112.cs | 28 ++++++ NzbDrone.Core/NzbDrone.Core.csproj | 4 + NzbDrone.Core/Providers/Indexer/Newznab.cs | 88 ++++++++++++++++ NzbDrone.Core/Providers/NewznzbProvider.cs | 95 ++++++++++++++++++ NzbDrone.Core/Repository/NewznabDefinition.cs | 20 ++++ .../Content/Images/Indexers/Newznab.png | Bin 0 -> 172 bytes NzbDrone.Web/Content/IndexerSettings.css | 86 ++++++++++++++++ .../Controllers/SettingsController.cs | 76 +++++++++++--- NzbDrone.Web/Models/IndexerSettingsModel.cs | 6 ++ NzbDrone.Web/NzbDrone.Web.csproj | 4 + NzbDrone.Web/Views/Settings/Indexers.cshtml | 71 ++++++++++++- .../Views/Settings/NewznabProvider.cshtml | 38 +++++++ 13 files changed, 501 insertions(+), 16 deletions(-) create mode 100644 NzbDrone.Core/Datastore/Migrations/Migration20111112.cs create mode 100644 NzbDrone.Core/Providers/Indexer/Newznab.cs create mode 100644 NzbDrone.Core/Providers/NewznzbProvider.cs create mode 100644 NzbDrone.Core/Repository/NewznabDefinition.cs create mode 100644 NzbDrone.Web/Content/Images/Indexers/Newznab.png create mode 100644 NzbDrone.Web/Content/IndexerSettings.css create mode 100644 NzbDrone.Web/Views/Settings/NewznabProvider.cshtml diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 3f38a2abf..373ee940d 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -70,6 +70,7 @@ namespace NzbDrone.Core Kernel.Bind().To(); Kernel.Bind().To(); Kernel.Bind().To(); + Kernel.Bind().To(); var indexers = Kernel.GetAll(); Kernel.Get().InitializeIndexers(indexers.ToList()); diff --git a/NzbDrone.Core/Datastore/Migrations/Migration20111112.cs b/NzbDrone.Core/Datastore/Migrations/Migration20111112.cs new file mode 100644 index 000000000..7c91c87f6 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migrations/Migration20111112.cs @@ -0,0 +1,28 @@ +using System; +using System.Data; +using Migrator.Framework; + +namespace NzbDrone.Core.Datastore.Migrations +{ + + [Migration(20111112)] + public class Migration2011112 : Migration + { + public override void Up() + { + Database.AddTable("NewznabDefinitions", new[] + { + new Column("Id", DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity), + new Column("Enable", DbType.Boolean, ColumnProperty.NotNull), + new Column("Name", DbType.String, ColumnProperty.Null), + new Column("Url", DbType.String, ColumnProperty.Null), + new Column("ApiKey", DbType.String, ColumnProperty.Null) + }); + } + + public override void Down() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index b294c0c0e..bb6fc96ef 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -197,6 +197,7 @@ + @@ -231,6 +232,8 @@ + + @@ -282,6 +285,7 @@ + diff --git a/NzbDrone.Core/Providers/Indexer/Newznab.cs b/NzbDrone.Core/Providers/Indexer/Newznab.cs new file mode 100644 index 000000000..481b8b87f --- /dev/null +++ b/NzbDrone.Core/Providers/Indexer/Newznab.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.ServiceModel.Syndication; +using System.Text.RegularExpressions; +using Ninject; +using NzbDrone.Core.Model; +using NzbDrone.Core.Model.Search; +using NzbDrone.Core.Providers.Core; + +namespace NzbDrone.Core.Providers.Indexer +{ + public class Newznab : IndexerBase + { + private readonly NewznabProvider _newznabProvider; + + [Inject] + public Newznab(HttpProvider httpProvider, ConfigProvider configProvider, NewznabProvider newznabProvider) + : base(httpProvider, configProvider) + { + _newznabProvider = newznabProvider; + } + + protected override string[] Urls + { + get { return GetUrls(); } + } + + public override string Name + { + get { return "Newznab"; } + } + + protected override string NzbDownloadUrl(SyndicationItem item) + { + return item.Id; + } + + protected override IList GetSearchUrls(SearchModel searchModel) + { + var searchUrls = new List(); + + foreach (var url in Urls) + { + if (searchModel.SearchType == SearchType.EpisodeSearch) + { + searchUrls.Add(String.Format("{0}&q={1}&season{2}&ep{3}", url, + searchModel.SeriesTitle, searchModel.SeasonNumber, searchModel.EpisodeNumber)); + } + + if (searchModel.SearchType == SearchType.SeasonSearch) + { + searchUrls.Add(String.Format("{0}&q={1}&season{2}", url, searchModel.SeriesTitle, searchModel.SeasonNumber)); + } + } + + return searchUrls; + } + + protected override EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult) + { + if (currentResult != null) + { + var sizeString = Regex.Match(item.Summary.Text, @">\d+\.\d{1,2} \w{2}", RegexOptions.IgnoreCase).Value; + + currentResult.Size = Parser.GetReportSize(sizeString); + } + return currentResult; + } + + private string[] GetUrls() + { + var urls = new List(); + var newznzbIndexers = _newznabProvider.Enabled(); + + foreach(var newznabDefinition in newznzbIndexers) + { + if (!String.IsNullOrWhiteSpace(newznabDefinition.ApiKey)) + urls.Add(String.Format("{0}/api?t=tvsearch&cat=5030,5040&apikey={1}", newznabDefinition.Url, + newznabDefinition.ApiKey)); + + else + urls.Add(String.Format("{0}/api?t=tvsearch&cat=5030,5040", newznabDefinition.Url)); + } + + return urls.ToArray(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/NewznzbProvider.cs b/NzbDrone.Core/Providers/NewznzbProvider.cs new file mode 100644 index 000000000..5cb92b52f --- /dev/null +++ b/NzbDrone.Core/Providers/NewznzbProvider.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Ninject; +using NLog; +using NzbDrone.Core.Providers.Indexer; +using NzbDrone.Core.Repository; +using PetaPoco; + +namespace NzbDrone.Core.Providers +{ + public class NewznabProvider + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private readonly IDatabase _database; + + [Inject] + public NewznabProvider(IDatabase database) + { + _database = database; + } + + public NewznabProvider() + { + + } + + public virtual List Enabled() + { + return _database.Fetch("WHERE Enable = 1"); + } + + public virtual List All() + { + return _database.Fetch(); + } + + public virtual int Save(NewznabDefinition definition) + { + //Cleanup the URL + definition.Url = (new Uri(definition.Url).ParentUriString()); + + if (definition.Id == 0) + { + Logger.Debug("Adding Newznab definitions for {0}", definition.Name); + return Convert.ToInt32(_database.Insert(definition)); + } + + else + { + Logger.Debug("Updating Newznab definitions for {0}", definition.Name); + return _database.Update(definition); + } + } + + public virtual void SaveAll(IEnumerable definitions) + { + var definitionsList = definitions.ToList(); + + //Cleanup the URL for each definition + definitionsList.ForEach(p => p.Url = (new Uri(p.Url).ParentUriString())); + + _database.UpdateMany(definitionsList); + } + + public virtual void InitializeNewznabIndexers(IList indexers) + { + Logger.Info("Initializing Newznab indexers. Count {0}", indexers.Count); + + var currentIndexers = All(); + + foreach (var feedProvider in indexers) + { + NewznabDefinition indexerLocal = feedProvider; + if (!currentIndexers.Exists(c => c.Name == indexerLocal.Name)) + { + var settings = new NewznabDefinition + { + Enable = false, + Name = indexerLocal.Name, + Url = indexerLocal.Url, + ApiKey = indexerLocal.ApiKey + }; + + Save(settings); + } + } + } + + public virtual void Delete(int id) + { + _database.Delete(id); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Repository/NewznabDefinition.cs b/NzbDrone.Core/Repository/NewznabDefinition.cs new file mode 100644 index 000000000..a197be4ad --- /dev/null +++ b/NzbDrone.Core/Repository/NewznabDefinition.cs @@ -0,0 +1,20 @@ +using System; +using PetaPoco; + +namespace NzbDrone.Core.Repository +{ + [TableName("NewznabDefinitions")] + [PrimaryKey("Id", autoIncrement = true)] + public class NewznabDefinition + { + public int Id { get; set; } + + public Boolean Enable { get; set; } + + public String Name { get; set; } + + public String Url { get; set; } + + public String ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Web/Content/Images/Indexers/Newznab.png b/NzbDrone.Web/Content/Images/Indexers/Newznab.png new file mode 100644 index 0000000000000000000000000000000000000000..a6ffe32385b8f503881a71364308df210a27cb0c GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`#hxyXAr*6yee%+OoOfVMa1@^r z+<1S_6h1c*J7F`|Ymt|DfY{zC93t!MCb^>bP0l+XkKZ s.QualityProfileId == profileId).Count() != 0) + return new JsonResult { Data = "Unable to delete Profile, it is still in use." }; + + _qualityProvider.Delete(profileId); + } + + catch (Exception) + { + return new JsonResult { Data = "failed" }; + } + + return new JsonResult { Data = "ok" }; } - public QualityModel GetUpdatedProfileList() + public ViewResult AddNewznabProvider() { - var profiles = _qualityProvider.All().ToList(); - var defaultQualityQualityProfileId = - Convert.ToInt32(_configProvider.GetValue("DefaultQualityProfile", profiles[0].QualityProfileId)); - var selectList = new SelectList(profiles, "QualityProfileId", "Name"); + var newznab = new NewznabDefinition + { + Enable = false, + Name = "Newznab Provider" + }; - return new QualityModel { DefaultQualityProfileId = defaultQualityQualityProfileId, QualityProfileSelectList = selectList }; + var id = _newznabProvider.Save(newznab); + newznab.Id = id; + + ViewData["ProviderId"] = id; + + return View("NewznabProvider", newznab); } - public JsonResult DeleteQualityProfile(int profileId) + public ActionResult GetNewznabProviderView(NewznabDefinition provider) + { + ViewData["ProviderId"] = provider.Id; + + return PartialView("NewznabProvider", provider); + } + + public JsonResult DeleteNewznabProvider(int providerId) { try { - if (_seriesProvider.GetAllSeries().Where(s => s.QualityProfileId == profileId).Count() != 0) - return new JsonResult { Data = "Unable to delete Profile, it is still in use." }; - - _qualityProvider.Delete(profileId); + _newznabProvider.Delete(providerId); } catch (Exception) @@ -307,6 +335,21 @@ namespace NzbDrone.Web.Controllers return new JsonResult { Data = "ok" }; } + public ActionResult SubMenu() + { + return PartialView(); + } + + public QualityModel GetUpdatedProfileList() + { + var profiles = _qualityProvider.All().ToList(); + var defaultQualityQualityProfileId = + Convert.ToInt32(_configProvider.GetValue("DefaultQualityProfile", profiles[0].QualityProfileId)); + var selectList = new SelectList(profiles, "QualityProfileId", "Name"); + + return new QualityModel { DefaultQualityProfileId = defaultQualityQualityProfileId, QualityProfileSelectList = selectList }; + } + public JsonResult AutoConfigureSab() { try @@ -354,6 +397,9 @@ namespace NzbDrone.Web.Controllers _configProvider.NewzbinUsername = data.NewzbinUsername; _configProvider.NewzbinPassword = data.NewzbinPassword; + if (data.NewznabDefinitions != null) + _newznabProvider.SaveAll(data.NewznabDefinitions); + return GetSuccessResult(); } diff --git a/NzbDrone.Web/Models/IndexerSettingsModel.cs b/NzbDrone.Web/Models/IndexerSettingsModel.cs index afee322d2..9d0a40635 100644 --- a/NzbDrone.Web/Models/IndexerSettingsModel.cs +++ b/NzbDrone.Web/Models/IndexerSettingsModel.cs @@ -71,5 +71,11 @@ namespace NzbDrone.Web.Models [DisplayName("Newzbin")] [Description("Enable downloading episodes from Newzbin")] public bool NewzbinEnabled { get; set; } + + [DisplayName("Newznab")] + [Description("Enable downloading episodes from Newznab Providers")] + public bool NewznabEnabled { get; set; } + + public List NewznabDefinitions { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index dee81fc2e..1b9b57c58 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -327,6 +327,7 @@ + @@ -961,6 +962,9 @@ + + +