Newznab providers can be configured by the end user.

pull/4/head
Mark McDowall 13 years ago
parent f3f2691b4d
commit 6c86f1dfdd

@ -70,6 +70,7 @@ namespace NzbDrone.Core
Kernel.Bind<IndexerBase>().To<NzbMatrix>();
Kernel.Bind<IndexerBase>().To<NzbsRUs>();
Kernel.Bind<IndexerBase>().To<Newzbin>();
Kernel.Bind<IndexerBase>().To<Newznab>();
var indexers = Kernel.GetAll<IndexerBase>();
Kernel.Get<IndexerProvider>().InitializeIndexers(indexers.ToList());

@ -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();
}
}
}

@ -197,6 +197,7 @@
<Compile Include="Datastore\MigrationLogger.cs" />
<Compile Include="Datastore\MigrationsHelper.cs" />
<Compile Include="Datastore\CustomeMapper.cs" />
<Compile Include="Datastore\Migrations\Migration20111112.cs" />
<Compile Include="Datastore\Migrations\Migration20111011.cs" />
<Compile Include="Datastore\Migrations\Migration20110909.cs" />
<Compile Include="Datastore\Migrations\Migration20110726.cs" />
@ -231,6 +232,8 @@
<Compile Include="Model\Xbmc\IconType.cs" />
<Compile Include="Providers\Converting\AtomicParsleyProvider.cs" />
<Compile Include="Providers\Converting\HandbrakeProvider.cs" />
<Compile Include="Providers\Indexer\Newznab.cs" />
<Compile Include="Providers\NewznzbProvider.cs" />
<Compile Include="Providers\ExternalNotification\Prowl.cs" />
<Compile Include="Providers\ProwlProvider.cs" />
<Compile Include="Providers\Core\ConfigFileProvider.cs" />
@ -282,6 +285,7 @@
<Compile Include="Providers\Jobs\UpdateInfoJob.cs" />
<Compile Include="Providers\SceneMappingProvider.cs" />
<Compile Include="Providers\Xbmc\EventClientProvider.cs" />
<Compile Include="Repository\NewznabDefinition.cs" />
<Compile Include="Repository\ExternalNotificationDefinition.cs" />
<Compile Include="Repository\JobDefinition.cs" />
<Compile Include="Repository\IndexerDefinition.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<string> GetSearchUrls(SearchModel searchModel)
{
var searchUrls = new List<String>();
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}</a>", RegexOptions.IgnoreCase).Value;
currentResult.Size = Parser.GetReportSize(sizeString);
}
return currentResult;
}
private string[] GetUrls()
{
var urls = new List<string>();
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();
}
}
}

@ -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<NewznabDefinition> Enabled()
{
return _database.Fetch<NewznabDefinition>("WHERE Enable = 1");
}
public virtual List<NewznabDefinition> All()
{
return _database.Fetch<NewznabDefinition>();
}
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<NewznabDefinition> 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<NewznabDefinition> 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<NewznabDefinition>(id);
}
}
}

@ -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; }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

@ -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;
}

@ -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();
}

@ -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<NewznabDefinition> NewznabDefinitions { get; set; }
}
}

@ -327,6 +327,7 @@
<Content Include="Content\2011.2.712\Windows7\slider-v.gif" />
<Content Include="Content\2011.2.712\Windows7\sprite-vertical.png" />
<Content Include="Content\2011.2.712\Windows7\sprite.png" />
<Content Include="Content\IndexerSettings.css" />
<Content Include="Content\Slider.css" />
<Content Include="Content\Grid.css" />
<Content Include="Content\Images\close.png" />
@ -961,6 +962,9 @@
<ItemGroup>
<Content Include="Views\Settings\Prowl.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Settings\NewznabProvider.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

@ -3,6 +3,7 @@
@section HeaderContent{
<link rel="stylesheet" type="text/css" href="../../Content/Settings.css" />
<link rel="stylesheet" type="text/css" href="../../Content/IndexerSettings.css" />
<style>
.indexerPanel
@ -32,7 +33,7 @@
<div>
@{ Html.Telerik().PanelBar()
.Name("PanelBar")
//.HtmlAttributes(new { style = "width: 500px; margin: 10px;" })
//.HtmlAttributes(new { style = "width: 500px; margin: 10px;" })
.ExpandMode(PanelBarExpandMode.Single)
.SelectedIndex(0)
.Items(indexerItem =>
@ -121,6 +122,27 @@
@Html.TextBoxFor(m => m.NewzbinPassword, new { @class = "inputClass" })
</div>
</text>);
indexerItem.Add()
.Text("Newznzb")
.ImageUrl("~/Content/Images/Indexers/Newznab.png")
.Content(@<text>
<div class="indexerPanel clearfix">
<label class="labelClass">Enable
<span class="small">@Html.DescriptionFor(m => m.NewznabEnabled)</span>
</label>
@Html.CheckBoxFor(m => m.NewznabEnabled, new { @class = "inputClass checkClass" })
</div>
<p></p>
<a id="addItem" href="@Url.Action("AddNewznabProvider", "Settings")">
<img src="../../Content/Images/Plus.png" alt="Add Newznab Provider" width="20px" height="20px" />
Add Newznab Provider</a>
<div id="newznabProviders">
@foreach (var provider in Model.NewznabDefinitions)
{
Html.RenderAction("GetNewznabProviderView", provider);
}
</div>
</text>);
}).Render();
}
</div>
@ -133,4 +155,51 @@
@section Scripts{
<script src="../../Scripts/NzbDrone/settingsForm.js" type="text/javascript"></script>
<script type="text/javascript">
$("#addItem").live('click', function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) {
$("#newznabProviders").append(html);
}
});
return false;
});
var deleteNewznabProviderUrl = '@Url.Action("DeleteNewznabProvider", "Settings")';
function deleteProvider(id) {
$.ajax({
type: "POST",
url: deleteNewznabProviderUrl,
data: jQuery.param({ providerId: id }),
error: function (req, status, error) {
alert("Sorry! We could not delete your Provider at this time. " + error);
},
success: function (data, textStatus, jqXHR) {
if (data == "ok") {
$("#provider_" + id).remove();
}
else {
alert(data);
}
}
});
}
function getProviderId(obj) {
var parentProviderSection = $(obj).parents('.providerSection');
return parentProviderSection.children('.newznabProviderId').val();
}
$(".providerName_textbox").live('keyup', function () {
var value = $(this).val();
var profileId = getProviderId(this);
$("#title_" + profileId).text(value);
}).keyup();
</script>
}

@ -0,0 +1,38 @@
@model NzbDrone.Core.Repository.NewznabDefinition
@using System.Collections
@using NzbDrone.Core.Repository.Quality
@using NzbDrone.Web.Helpers
@{
Layout = null;
}
@using (Html.BeginCollectionItem("NewznabDefinitions"))
{
var idClean = ViewData.TemplateInfo.HtmlFieldPrefix.Replace('[', '_').Replace(']', '_');
<div class="providerSection" id="provider_@(Model.Id)">
<div class="providerHeader">
<span class="titleText" id="title_@(Model.Id)">
@Model.Name
</span>
<a href="#" id="@Model.Id" class="deleteProvider" onclick="deleteProvider('@(Model.Id)'); return false;">
<img src="../../Content/Images/X.png" alt="Delete" width="22px" height="22px" /></a>
</div>
<div class="providerOptions">
@Html.Label("Enabled")
@Html.CheckBoxFor(m => m.Enable)
@Html.LabelFor(x => x.Name)
@Html.TextBoxFor(x => x.Name, new {@class = "providerName_textbox"})
@Html.LabelFor(x => x.Url)
@Html.TextBoxFor(m => m.Url)
@Html.LabelFor(x => x.ApiKey)
@Html.TextBoxFor(m => m.ApiKey)
</div>
@Html.HiddenFor(x => x.Id, new {@class = "newznabProviderId"})
@Html.Hidden("cleanId", idClean, new {@class = "cleanId"})
</div>
}
Loading…
Cancel
Save