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 000000000..a6ffe3238 Binary files /dev/null and b/NzbDrone.Web/Content/Images/Indexers/Newznab.png differ diff --git a/NzbDrone.Web/Content/IndexerSettings.css b/NzbDrone.Web/Content/IndexerSettings.css new file mode 100644 index 000000000..617199c66 --- /dev/null +++ b/NzbDrone.Web/Content/IndexerSettings.css @@ -0,0 +1,86 @@ +#bottom +{ + margin-top: -10px; + margin-left: 15px; +} + +#addItem +{ + text-decoration: none; + font-size:16px; + color: black; + font-weight:bold; +} + +.titleText +{ + font-size: 1.5em; + line-height: 1; + margin-bottom: 1em; + display:inline; + position: absolute; + top: -1px; + left: 2px; + padding-bottom: 0px; + white-space:nowrap; +} + +.providerHeader +{ + min-height: 23px; + position: relative; +} + +.providerSection +{ + float: left; + width: 270px; + margin: 2px; + border:solid 1px #CCCCCD; + display: inline-block; + overflow: hidden; + padding: 5px 5px 5px 5px; +} + +.providerOptions label +{ + margin-top: 10px; + margin-left: 7px; + margin-right: 25px; + float: left; + font-weight: bold; + width: 50px; +} + +.providerOptions input, .providerOptions select +{ + font-size:12px; + padding:4px 2px; + border:solid 1px #aacfe4; + width:170px; + margin-right: 2px; +} + +.providerOptions select +{ + width:176px; +} + +.deleteProvider +{ + position: absolute; + top: 0px; + right: 0px; +} + +input[type="checkbox"] +{ + height: 25px; +} + +#newznabProviders +{ + overflow: hidden; + margin-top: 5px; + margin-bottom: 10px; +} \ No newline at end of file diff --git a/NzbDrone.Web/Controllers/SettingsController.cs b/NzbDrone.Web/Controllers/SettingsController.cs index d7bea1647..1e7f907be 100644 --- a/NzbDrone.Web/Controllers/SettingsController.cs +++ b/NzbDrone.Web/Controllers/SettingsController.cs @@ -30,17 +30,19 @@ namespace NzbDrone.Web.Controllers private readonly QualityTypeProvider _qualityTypeProvider; private readonly RootDirProvider _rootDirProvider; private readonly ConfigFileProvider _configFileProvider; + private readonly NewznabProvider _newznabProvider; public SettingsController(ConfigProvider configProvider, IndexerProvider indexerProvider, QualityProvider qualityProvider, AutoConfigureProvider autoConfigureProvider, SeriesProvider seriesProvider, ExternalNotificationProvider externalNotificationProvider, QualityTypeProvider qualityTypeProvider, RootDirProvider rootDirProvider, - ConfigFileProvider configFileProvider) + ConfigFileProvider configFileProvider, NewznabProvider newznabProvider) { _externalNotificationProvider = externalNotificationProvider; _qualityTypeProvider = qualityTypeProvider; _rootDirProvider = rootDirProvider; _configFileProvider = configFileProvider; + _newznabProvider = newznabProvider; _configProvider = configProvider; _indexerProvider = indexerProvider; _qualityProvider = qualityProvider; @@ -86,7 +88,10 @@ namespace NzbDrone.Web.Controllers NzbsOrgEnabled = _indexerProvider.GetSettings(typeof(NzbsOrg)).Enable, NzbMatrixEnabled = _indexerProvider.GetSettings(typeof(NzbMatrix)).Enable, NzbsRUsEnabled = _indexerProvider.GetSettings(typeof(NzbsRUs)).Enable, - NewzbinEnabled = _indexerProvider.GetSettings(typeof(Newzbin)).Enable + NewzbinEnabled = _indexerProvider.GetSettings(typeof(Newzbin)).Enable, + NewznabEnabled = _indexerProvider.GetSettings(typeof(Newzbin)).Enable, + + NewznabDefinitions = _newznabProvider.All(), }); } @@ -274,29 +279,52 @@ namespace NzbDrone.Web.Controllers return PartialView("QualityProfileItem", profile); } - public ActionResult SubMenu() + public JsonResult DeleteQualityProfile(int profileId) { - return PartialView(); + 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); + } + + 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 @@ + + +