1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-04 10:02:40 +01:00

Merge branch 'thingi-provider' into develop

This commit is contained in:
Mark McDowall 2013-10-02 14:51:37 -07:00
commit 2fc8123d6b
86 changed files with 1076 additions and 618 deletions

View File

@ -20,14 +20,14 @@ namespace Marr.Data.Converters
{
public class BooleanIntConverter : IConverter
{
public object FromDB(ColumnMap map, object dbValue)
public object FromDB(ConverterContext context)
{
if (dbValue == DBNull.Value)
if (context.DbValue == DBNull.Value)
{
return DBNull.Value;
}
int val = (int)dbValue;
int val = (int)context.DbValue;
if (val == 1)
{
@ -40,7 +40,12 @@ public object FromDB(ColumnMap map, object dbValue)
throw new ConversionException(
string.Format(
"The BooleanCharConverter could not convert the value '{0}' to a boolean.",
dbValue));
context.DbValue));
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)

View File

@ -20,14 +20,14 @@ namespace Marr.Data.Converters
{
public class BooleanYNConverter : IConverter
{
public object FromDB(ColumnMap map, object dbValue)
public object FromDB(ConverterContext context)
{
if (dbValue == DBNull.Value)
if (context.DbValue == DBNull.Value)
{
return DBNull.Value;
}
string val = dbValue.ToString();
string val = context.DbValue.ToString();
if (val == "Y")
{
@ -40,7 +40,12 @@ public object FromDB(ColumnMap map, object dbValue)
throw new ConversionException(
string.Format(
"The BooleanYNConverter could not convert the value '{0}' to a boolean.",
dbValue));
context.DbValue));
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext {ColumnMap = map, DbValue = dbValue});
}
public object ToDB(object clrValue)

View File

@ -30,10 +30,15 @@ public Type DbType
get { return typeof(TDb); }
}
public object FromDB(ConverterContext context)
{
TDb val = (TDb)context.DbValue;
return val.ToType(typeof(TClr), CultureInfo.InvariantCulture);
}
public object FromDB(ColumnMap map, object dbValue)
{
TDb val = (TDb)dbValue;
return val.ToType(typeof(TClr), CultureInfo.InvariantCulture);
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)

View File

@ -0,0 +1,13 @@
using System.Data;
using Marr.Data.Mapping;
namespace Marr.Data.Converters
{
public class ConverterContext
{
public ColumnMap ColumnMap { get; set; }
public object DbValue { get; set; }
public ColumnMapCollection MapCollection { get; set; }
public IDataRecord DataRecord { get; set; }
}
}

View File

@ -20,11 +20,16 @@ namespace Marr.Data.Converters
{
public class EnumIntConverter : IConverter
{
public object FromDB(ConverterContext context)
{
if (context.DbValue == null || context.DbValue == DBNull.Value)
return null;
return Enum.ToObject(context.ColumnMap.FieldType, (int)context.DbValue);
}
public object FromDB(ColumnMap map, object dbValue)
{
if (dbValue == null || dbValue == DBNull.Value)
return null;
return Enum.ToObject(map.FieldType, (int)dbValue);
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)

View File

@ -20,11 +20,16 @@ namespace Marr.Data.Converters
{
public class EnumStringConverter : IConverter
{
public object FromDB(ConverterContext context)
{
if (context.DbValue == null || context.DbValue == DBNull.Value)
return null;
return Enum.Parse(context.ColumnMap.FieldType, (string)context.DbValue);
}
public object FromDB(ColumnMap map, object dbValue)
{
if (dbValue == null || dbValue == DBNull.Value)
return null;
return Enum.Parse(map.FieldType, (string)dbValue);
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)

View File

@ -20,6 +20,9 @@ namespace Marr.Data.Converters
{
public interface IConverter
{
object FromDB(ConverterContext context);
[Obsolete("use FromDB(ConverterContext context) instead")]
object FromDB(ColumnMap map, object dbValue);
object ToDB(object clrValue);
Type DbType { get; }

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Data.Common;
using Marr.Data.Converters;
namespace Marr.Data.Mapping
{
@ -53,7 +54,15 @@ public object LoadExistingEntity(ColumnMapCollection mappings, DbDataReader read
// Handle conversions
if (dataMap.Converter != null)
{
dbValue = dataMap.Converter.FromDB(dataMap, dbValue);
var convertContext = new ConverterContext
{
DbValue = dbValue,
ColumnMap = dataMap,
MapCollection = mappings,
DataRecord = reader
};
dbValue = dataMap.Converter.FromDB(convertContext);
}
if (dbValue != DBNull.Value && dbValue != null)

View File

@ -52,6 +52,7 @@
<Compile Include="Converters\BooleanYNConverter.cs" />
<Compile Include="Converters\CastConverter.cs" />
<Compile Include="Converters\ConversionException.cs" />
<Compile Include="Converters\ConverterContext.cs" />
<Compile Include="Converters\EnumIntConverter.cs" />
<Compile Include="Converters\EnumStringConverter.cs" />
<Compile Include="Converters\IConverter.cs" />

View File

@ -12,7 +12,7 @@ public class SchemaBuilderFixture : TestBase
[Test]
public void should_return_field_for_every_property()
{
var schema = SchemaBuilder.GenerateSchema(new TestModel());
var schema = SchemaBuilder.ToSchema(new TestModel());
schema.Should().HaveCount(2);
}
@ -26,7 +26,7 @@ public void schema_should_have_proper_fields()
LastName = "Poop"
};
var schema = SchemaBuilder.GenerateSchema(model);
var schema = SchemaBuilder.ToSchema(model);
schema.Should().Contain(c => c.Order == 1 && c.Name == "LastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string) c.Value == "Poop");
schema.Should().Contain(c => c.Order == 0 && c.Name == "FirstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string) c.Value == "Bob");

View File

@ -36,7 +36,7 @@ public class ResourceMappingFixture : TestBase
[TestCase(typeof(Episode), typeof(EpisodeResource))]
[TestCase(typeof(RootFolder), typeof(RootFolderResource))]
[TestCase(typeof(NamingConfig), typeof(NamingConfigResource))]
[TestCase(typeof(Indexer), typeof(IndexerResource))]
[TestCase(typeof(IndexerDefinition), typeof(IndexerResource))]
[TestCase(typeof(ReleaseInfo), typeof(ReleaseResource))]
[TestCase(typeof(ParsedEpisodeInfo), typeof(ReleaseResource))]
[TestCase(typeof(DownloadDecision), typeof(ReleaseResource))]

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.Annotations;
@ -8,8 +10,10 @@ namespace NzbDrone.Api.ClientSchema
{
public static class SchemaBuilder
{
public static List<Field> GenerateSchema(object model)
public static List<Field> ToSchema(object model)
{
Ensure.That(() => model).IsNotNull();
var properties = model.GetType().GetSimpleProperties();
var result = new List<Field>(properties.Count);
@ -50,10 +54,55 @@ public static List<Field> GenerateSchema(object model)
}
public static object ReadFormSchema(List<Field> fields, Type targetType)
{
Ensure.That(() => targetType).IsNotNull();
var properties = targetType.GetSimpleProperties();
var target = Activator.CreateInstance(targetType);
foreach (var propertyInfo in properties)
{
var fieldAttribute = propertyInfo.GetAttribute<FieldDefinitionAttribute>(false);
if (fieldAttribute != null)
{
var field = fields.Find(f => f.Name == propertyInfo.Name);
if (propertyInfo.PropertyType == typeof(Int32))
{
var intValue = Convert.ToInt32(field.Value);
propertyInfo.SetValue(target, intValue, null);
}
else if (propertyInfo.PropertyType == typeof(Nullable<Int32>))
{
var intValue = field.Value.ToString().ParseInt32();
propertyInfo.SetValue(target, intValue, null);
}
else
{
propertyInfo.SetValue(target, field.Value, null);
}
}
}
return target;
}
public static T ReadFormSchema<T>(List<Field> fields)
{
return (T)ReadFormSchema(fields, typeof (T));
}
private static List<SelectOption> GetSelectOptions(Type selectOptions)
{
var options = from Enum e in Enum.GetValues(selectOptions)
select new SelectOption { Value = Convert.ToInt32(e), Name = e.ToString() };
select new SelectOption { Value = Convert.ToInt32(e), Name = e.ToString() };
return options.OrderBy(o => o.Value).ToList();
}

View File

@ -8,38 +8,6 @@ namespace NzbDrone.Api.ClientSchema
{
public static class SchemaDeserializer
{
public static T DeserializeSchema<T>(T model, List<Field> fields)
{
var properties = model.GetType().GetSimpleProperties();
foreach (var propertyInfo in properties)
{
var fieldAttribute = propertyInfo.GetAttribute<FieldDefinitionAttribute>(false);
if (fieldAttribute != null)
{
var field = fields.Find(f => f.Name == propertyInfo.Name);
if (propertyInfo.PropertyType == typeof (Int32))
{
var intValue = Convert.ToInt32(field.Value);
propertyInfo.SetValue(model, intValue, null);
}
else if (propertyInfo.PropertyType == typeof(Nullable<Int32>))
{
var intValue = field.Value.ToString().ParseInt32();
propertyInfo.SetValue(model, intValue, null);
}
else
{
propertyInfo.SetValue(model, field.Value, null);
}
}
}
return model;
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Api.REST;
namespace NzbDrone.Api
{
public class ProviderResource : RestResource
{
public Boolean Enable { get; set; }
public String Name { get; set; }
public List<Field> Fields { get; set; }
public String Implementation { get; set; }
public String ConfigContract { get; set; }
}
}

View File

@ -1,113 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Api.REST;
using NzbDrone.Core.Indexers;
using Omu.ValueInjecter;
using FluentValidation;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.Indexers
{
public class IndexerModule : NzbDroneRestModule<IndexerResource>
public class IndexerModule : ProviderModuleBase<ProviderResource, IIndexer, IndexerDefinition>
{
private readonly IIndexerService _indexerService;
public IndexerModule(IIndexerService indexerService)
public IndexerModule(IndexerFactory indexerFactory)
: base(indexerFactory, "indexer")
{
_indexerService = indexerService;
GetResourceAll = GetAll;
GetResourceById = GetIndexer;
CreateResource = CreateIndexer;
UpdateResource = UpdateIndexer;
DeleteResource = DeleteIndexer;
SharedValidator.RuleFor(c => c.Name).NotEmpty();
SharedValidator.RuleFor(c => c.Implementation).NotEmpty();
PostValidator.RuleFor(c => c.Fields).NotEmpty();
}
private IndexerResource GetIndexer(int id)
{
return _indexerService.Get(id).InjectTo<IndexerResource>();
}
private List<IndexerResource> GetAll()
{
var indexers = _indexerService.All();
var result = new List<IndexerResource>(indexers.Count);
foreach (var indexer in indexers)
{
var indexerResource = new IndexerResource();
indexerResource.InjectFrom(indexer);
indexerResource.Fields = SchemaBuilder.GenerateSchema(indexer.Settings);
result.Add(indexerResource);
}
return result;
}
private int CreateIndexer(IndexerResource indexerResource)
{
var indexer = GetIndexer(indexerResource);
indexer = _indexerService.Create(indexer);
return indexer.Id;
}
private void UpdateIndexer(IndexerResource indexerResource)
{
var indexer = _indexerService.Get(indexerResource.Id);
indexer.InjectFrom(indexerResource);
indexer.Settings = SchemaDeserializer.DeserializeSchema(indexer.Settings, indexerResource.Fields);
ValidateIndexer(indexer);
_indexerService.Update(indexer);
}
private static void ValidateIndexer(Indexer indexer)
{
if (indexer.Enable)
{
var validationResult = indexer.Settings.Validate();
if (!validationResult.IsValid)
{
throw new ValidationException(validationResult.Errors);
}
}
}
private Indexer GetIndexer(IndexerResource indexerResource)
{
var indexer = _indexerService.Schema()
.SingleOrDefault(i =>
i.Implementation.Equals(indexerResource.Implementation,
StringComparison.InvariantCultureIgnoreCase));
if (indexer == null)
{
throw new BadRequestException("Invalid Indexer Implementation");
}
indexer.InjectFrom(indexerResource);
indexer.Settings = SchemaDeserializer.DeserializeSchema(indexer.Settings, indexerResource.Fields);
ValidateIndexer(indexer);
return indexer;
}
private void DeleteIndexer(int id)
{
_indexerService.Delete(id);
}
}
}

View File

@ -11,5 +11,6 @@ public class IndexerResource : RestResource
public String Name { get; set; }
public List<Field> Fields { get; set; }
public String Implementation { get; set; }
public String ConfigContract { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Core.Indexers;
using Omu.ValueInjecter;
@ -7,26 +8,28 @@ namespace NzbDrone.Api.Indexers
{
public class IndexerSchemaModule : NzbDroneRestModule<IndexerResource>
{
private readonly IIndexerService _indexerService;
private readonly IIndexerFactory _indexerFactory;
public IndexerSchemaModule(IIndexerService indexerService)
public IndexerSchemaModule(IIndexerFactory indexerFactory)
: base("indexer/schema")
{
_indexerService = indexerService;
_indexerFactory = indexerFactory;
GetResourceAll = GetSchema;
}
private List<IndexerResource> GetSchema()
{
var indexers = _indexerService.Schema();
var result = new List<IndexerResource>(indexers.Count);
var indexers = _indexerFactory.Templates().Where(c => c.Implementation =="Newznab");
var result = new List<IndexerResource>(indexers.Count());
foreach (var indexer in indexers)
{
var indexerResource = new IndexerResource();
indexerResource.InjectFrom(indexer);
indexerResource.Fields = SchemaBuilder.GenerateSchema(indexer.Settings);
indexerResource.Fields = SchemaBuilder.ToSchema(indexer.Settings);
result.Add(indexerResource);
}

View File

@ -4,6 +4,7 @@
using NzbDrone.Api.ClientSchema;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.REST;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.Notifications;
using Omu.ValueInjecter;
@ -39,7 +40,7 @@ private List<NotificationResource> GetAll()
{
var notificationResource = new NotificationResource();
notificationResource.InjectFrom(notification);
notificationResource.Fields = SchemaBuilder.GenerateSchema(notification.Settings);
notificationResource.Fields = SchemaBuilder.ToSchema(notification.Settings);
notificationResource.TestCommand = String.Format("test{0}", notification.Implementation.ToLowerInvariant());
result.Add(notificationResource);
@ -79,7 +80,10 @@ private Notification ConvertToNotification(NotificationResource notificationReso
}
notification.InjectFrom(notificationResource);
notification.Settings = SchemaDeserializer.DeserializeSchema(notification.Settings, notificationResource.Fields);
//var configType = ReflectionExtensions.CoreAssembly.FindTypeByName(notification)
//notification.Settings = SchemaBuilder.ReadFormSchema(notification.Settings, notificationResource.Fields);
return notification;
}

View File

@ -28,7 +28,7 @@ private List<NotificationResource> GetSchema()
{
var notificationResource = new NotificationResource();
notificationResource.InjectFrom(notification);
notificationResource.Fields = SchemaBuilder.GenerateSchema(notification.Settings);
notificationResource.Fields = SchemaBuilder.ToSchema(notification.Settings);
notificationResource.TestCommand = String.Format("test{0}", notification.Implementation.ToLowerInvariant());
result.Add(notificationResource);

View File

@ -109,6 +109,8 @@
<Compile Include="Frontend\StaticResourceModule.cs" />
<Compile Include="History\HistoryResource.cs" />
<Compile Include="History\HistoryModule.cs" />
<Compile Include="IndexerResource.cs" />
<Compile Include="ProviderModuleBase.cs" />
<Compile Include="Indexers\IndexerSchemaModule.cs" />
<Compile Include="Indexers\IndexerModule.cs" />
<Compile Include="Indexers\IndexerResource.cs" />

View File

@ -0,0 +1,134 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using Nancy;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Indexers;
using NzbDrone.Api.Mapping;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.ThingiProvider;
using Omu.ValueInjecter;
namespace NzbDrone.Api
{
public abstract class ProviderModuleBase<TProviderResource, TProvider, TProviderDefinition> : NzbDroneRestModule<TProviderResource>
where TProviderDefinition : ProviderDefinition, new()
where TProvider : IProvider
where TProviderResource : ProviderResource, new()
{
private readonly IProviderFactory<TProvider, TProviderDefinition> _providerFactory;
protected ProviderModuleBase(IProviderFactory<TProvider, TProviderDefinition> providerFactory, string resource)
: base(resource)
{
_providerFactory = providerFactory;
Get["templates"] = x => GetTemplates();
GetResourceAll = GetAll;
GetResourceById = GetProviderById;
CreateResource = CreateProvider;
UpdateResource = UpdateProvider;
DeleteResource = DeleteProvider;
SharedValidator.RuleFor(c => c.Name).NotEmpty();
SharedValidator.RuleFor(c => c.Implementation).NotEmpty();
SharedValidator.RuleFor(c => c.ConfigContract).NotEmpty();
PostValidator.RuleFor(c => c.Fields).NotEmpty();
}
private TProviderResource GetProviderById(int id)
{
return _providerFactory.Get(id).InjectTo<TProviderResource>();
}
private List<TProviderResource> GetAll()
{
var indexerDefinitions = _providerFactory.All();
var result = new List<TProviderResource>(indexerDefinitions.Count);
foreach (var definition in indexerDefinitions)
{
var indexerResource = new TProviderResource();
indexerResource.InjectFrom(definition);
indexerResource.Fields = SchemaBuilder.ToSchema(definition.Settings);
result.Add(indexerResource);
}
return result;
}
private int CreateProvider(TProviderResource indexerResource)
{
var indexer = GetDefinition(indexerResource);
indexer = _providerFactory.Create(indexer);
return indexer.Id;
}
private void UpdateProvider(TProviderResource indexerResource)
{
var indexer = GetDefinition(indexerResource);
ValidateIndexer(indexer);
_providerFactory.Update(indexer);
}
private static void ValidateIndexer(ProviderDefinition definition)
{
if (!definition.Enable) return;
var validationResult = definition.Settings.Validate();
if (!validationResult.IsValid)
{
throw new ValidationException(validationResult.Errors);
}
}
private TProviderDefinition GetDefinition(TProviderResource indexerResource)
{
var definition = new TProviderDefinition();
definition.InjectFrom(indexerResource);
var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract);
definition.Settings = (IProviderConfig)SchemaBuilder.ReadFormSchema(indexerResource.Fields, configContract);
ValidateIndexer(definition);
return definition;
}
private void DeleteProvider(int id)
{
_providerFactory.Delete(id);
}
private Response GetTemplates()
{
var indexers = _providerFactory.Templates();
var result = new List<IndexerResource>(indexers.Count());
foreach (var indexer in indexers)
{
var indexerResource = new IndexerResource();
indexerResource.InjectFrom(indexer);
indexerResource.Fields = SchemaBuilder.ToSchema(indexer.Settings);
result.Add(indexerResource);
}
return result.AsResponse();
}
}
}

View File

@ -37,7 +37,7 @@ public void container_should_inject_itself()
{
var factory = MainAppContainerBuilder.BuildContainer(args).Resolve<IServiceFactory>();
factory.Build<IIndexerService>().Should().NotBeNull();
factory.Build<IIndexerFactory>().Should().NotBeNull();
}
[Test]

View File

@ -7,6 +7,8 @@ namespace NzbDrone.Common.Reflection
{
public static class ReflectionExtensions
{
public static readonly Assembly CoreAssembly = Assembly.Load("NzbDrone.Core");
public static List<PropertyInfo> GetSimpleProperties(this Type type)
{
var properties = type.GetProperties();
@ -58,6 +60,11 @@ public static T GetAttribute<T>(this MemberInfo member, bool isRequired = true)
return (T)attribute;
}
public static Type FindTypeByName(this Assembly assembly, string name)
{
return assembly.GetTypes().SingleOrDefault(c => c.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
}
public static bool HasAttribute<TAttribute>(this Type type)
{
return type.GetCustomAttributes(typeof(TAttribute), true).Any();

View File

@ -0,0 +1,39 @@
using System;
using FluentAssertions;
using Marr.Data.Converters;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class ProviderSettingConverterFixture : CoreTest<ProviderSettingConverter>
{
[Test]
public void should_return_null_config_if_config_is_null()
{
var result = Subject.FromDB(new ConverterContext()
{
DbValue = DBNull.Value
});
result.Should().Be(NullConfig.Instance);
}
[TestCase(null)]
[TestCase("")]
public void should_return_null_config_if_config_is_empty(object dbValue)
{
var result = Subject.FromDB(new ConverterContext()
{
DbValue = dbValue
});
result.Should().Be(NullConfig.Instance);
}
}
}

View File

@ -13,7 +13,7 @@
namespace NzbDrone.Core.Test.IndexerTests
{
public class IndexerServiceFixture : DbTest<IndexerService, IndexerDefinition>
public class IndexerServiceFixture : DbTest<IndexerFactory, IndexerDefinition>
{
private List<IIndexer> _indexers;
@ -57,10 +57,8 @@ public void getting_list_of_indexers()
var indexers = Subject.All().ToList();
indexers.Should().NotBeEmpty();
indexers.Should().NotContain(c => c.Settings == null);
indexers.Should().NotContain(c => c.Instance == null);
indexers.Should().NotContain(c => c.Name == null);
indexers.Select(c => c.Name).Should().OnlyHaveUniqueItems();
indexers.Select(c => c.Instance).Should().OnlyHaveUniqueItems();
}
@ -73,6 +71,7 @@ public void should_remove_missing_indexers_on_startup()
var existingIndexers = Builder<IndexerDefinition>.CreateNew().BuildNew();
existingIndexers.ConfigContract = typeof (NewznabSettings).Name;
repo.Insert(existingIndexers);

View File

@ -7,6 +7,7 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NUnit.Framework;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Test.Common.Categories;
using System.Linq;
@ -27,6 +28,12 @@ public void wombles_rss()
{
var indexer = new Wombles();
indexer.Definition = new IndexerDefinition
{
Name = "Wombles",
Settings = NullConfig.Instance
};
var result = Subject.FetchRss(indexer);
ValidateResult(result, skipSize: true, skipInfo: true);
@ -37,6 +44,11 @@ public void wombles_rss()
public void extv_rss()
{
var indexer = new Eztv();
indexer.Definition = new IndexerDefinition
{
Name = "Eztv",
Settings = NullConfig.Instance
};
var result = Subject.FetchRss(indexer);
@ -48,14 +60,14 @@ public void extv_rss()
public void nzbsorg_rss()
{
var indexer = new Newznab();
indexer.Settings = new NewznabSettings
{
ApiKey = "64d61d3cfd4b75e51d01cbc7c6a78275",
Url = "http://nzbs.org"
};
indexer.InstanceDefinition = new IndexerDefinition();
indexer.InstanceDefinition.Name = "nzbs.org";
indexer.Definition = new IndexerDefinition();
indexer.Definition.Name = "nzbs.org";
indexer.Definition.Settings = new NewznabSettings
{
ApiKey = "64d61d3cfd4b75e51d01cbc7c6a78275",
Url = "http://nzbs.org"
};
var result = Subject.FetchRss(indexer);

View File

@ -102,6 +102,7 @@
<Compile Include="DataAugmentationFixture\Scene\SceneMappingProxyFixture.cs" />
<Compile Include="DataAugmentationFixture\Scene\SceneMappingServiceFixture.cs" />
<Compile Include="Datastore\BasicRepositoryFixture.cs" />
<Compile Include="Datastore\Converters\ProviderSettingConverterFixture.cs" />
<Compile Include="Datastore\DatabaseRelationshipFixture.cs" />
<Compile Include="Datastore\MappingExtentionFixture.cs" />
<Compile Include="Datastore\ObjectDatabaseFixture.cs" />
@ -181,6 +182,8 @@
<Compile Include="ProviderTests\RecycleBinProviderTests\DeleteDirectoryFixture.cs" />
<Compile Include="NotificationTests\PlexProviderTest.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodesFixture.cs" />
<Compile Include="ThingiProviderTests\NullConfigFixture.cs" />
<Compile Include="ThingiProvider\ProviderBaseFixture.cs" />
<Compile Include="TvTests\RefreshEpisodeServiceFixture.cs" />
<Compile Include="TvTests\EpisodeProviderTests\HandleEpisodeFileDeletedFixture.cs" />
<Compile Include="TvTests\EpisodeRepositoryTests\FindEpisodeFixture.cs" />

View File

@ -0,0 +1,31 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Test.ThingiProvider
{
public class ProviderRepositoryFixture : DbTest<IndexerRepository, IndexerDefinition>
{
[Test]
public void should_read_write_download_provider()
{
var model = Builder<IndexerDefinition>.CreateNew().BuildNew();
var newznabSettings = Builder<NewznabSettings>.CreateNew().Build();
model.Settings = newznabSettings;
Subject.Insert(model);
var storedProvider = Subject.Single();
storedProvider.Settings.Should().BeOfType<NewznabSettings>();
var storedSetting = (NewznabSettings)storedProvider.Settings;
storedSetting.ShouldHave().AllProperties().EqualTo(newznabSettings);
}
}
}

View File

@ -0,0 +1,17 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Test.ThingiProviderTests
{
[TestFixture]
public class NullConfigFixture : CoreTest<NullConfig>
{
[Test]
public void should_be_valid()
{
Subject.Validate().IsValid.Should().BeTrue();
}
}
}

View File

@ -6,14 +6,14 @@ namespace NzbDrone.Core.Datastore.Converters
{
public class BooleanIntConverter : IConverter
{
public object FromDB(ColumnMap map, object dbValue)
public object FromDB(ConverterContext context)
{
if (dbValue == DBNull.Value)
if (context.DbValue == DBNull.Value)
{
return DBNull.Value;
}
var val = (Int64)dbValue;
var val = (Int64)context.DbValue;
switch (val)
{
@ -22,10 +22,15 @@ public object FromDB(ColumnMap map, object dbValue)
case 0:
return false;
default:
throw new ConversionException(string.Format("The BooleanCharConverter could not convert the value '{0}' to a Boolean.", dbValue));
throw new ConversionException(string.Format("The BooleanCharConverter could not convert the value '{0}' to a Boolean.", context.DbValue));
}
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)
{
var val = (Nullable<bool>)clrValue;

View File

@ -7,22 +7,26 @@ namespace NzbDrone.Core.Datastore.Converters
{
public class EmbeddedDocumentConverter : IConverter
{
public object FromDB(ColumnMap map, object dbValue)
public virtual object FromDB(ConverterContext context)
{
if (dbValue == DBNull.Value)
if (context.DbValue == DBNull.Value)
{
return DBNull.Value;
}
var stringValue = (string)dbValue;
var stringValue = (string)context.DbValue;
if (string.IsNullOrWhiteSpace(stringValue))
{
return null;
}
return Json.Deserialize(stringValue, map.FieldType);
return Json.Deserialize(stringValue, context.ColumnMap.FieldType);
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)

View File

@ -14,16 +14,21 @@ public Type DbType
}
}
public object FromDB(ColumnMap map, object dbValue)
public object FromDB(ConverterContext context)
{
if (dbValue != null && dbValue != DBNull.Value)
if (context.DbValue != null && context.DbValue != DBNull.Value)
{
return Enum.ToObject(map.FieldType, (Int64)dbValue);
return Enum.ToObject(context.ColumnMap.FieldType, (Int64)context.DbValue);
}
return null;
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)
{
if (clrValue != null)

View File

@ -6,6 +6,21 @@ namespace NzbDrone.Core.Datastore.Converters
{
public class Int32Converter : IConverter
{
public object FromDB(ConverterContext context)
{
if (context.DbValue == DBNull.Value)
{
return DBNull.Value;
}
if (context.DbValue is Int32)
{
return context.DbValue;
}
return Convert.ToInt32(context.DbValue);
}
public object FromDB(ColumnMap map, object dbValue)
{
if (dbValue == DBNull.Value)

View File

@ -0,0 +1,40 @@
using System;
using Marr.Data.Converters;
using NzbDrone.Common.Reflection;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Datastore.Converters
{
public class ProviderSettingConverter : EmbeddedDocumentConverter
{
public override object FromDB(ConverterContext context)
{
if (context.DbValue == DBNull.Value)
{
return NullConfig.Instance;
}
var stringValue = (string)context.DbValue;
if (string.IsNullOrWhiteSpace(stringValue))
{
return NullConfig.Instance;
}
var ordinal = context.DataRecord.GetOrdinal("ConfigContract");
var contract = context.DataRecord.GetString(ordinal);
var impType = typeof (IProviderConfig).Assembly.FindTypeByName(contract);
if (impType == null)
{
throw new ConfigContractNotFoundException(contract);
}
return Json.Deserialize(stringValue, impType);
}
}
}

View File

@ -7,18 +7,23 @@ namespace NzbDrone.Core.Datastore.Converters
{
public class QualityIntConverter : IConverter
{
public object FromDB(ColumnMap map, object dbValue)
public object FromDB(ConverterContext context)
{
if (dbValue == DBNull.Value)
if (context.DbValue == DBNull.Value)
{
return Quality.Unknown;
}
var val = Convert.ToInt32(dbValue);
var val = Convert.ToInt32(context.DbValue);
return (Quality)val;
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)
{
if(clrValue == null) return 0;

View File

@ -6,9 +6,14 @@ namespace NzbDrone.Core.Datastore.Converters
{
public class UtcConverter : IConverter
{
public object FromDB(ConverterContext context)
{
return context.DbValue;
}
public object FromDB(ColumnMap map, object dbValue)
{
return dbValue;
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)

View File

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Data;
using FluentMigrator;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(22)]
public class move_indexer_to_generic_provider : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Indexers").AddColumn("ConfigContract").AsString().Nullable();
//Execute.WithConnection(ConvertSeasons);
}
private void ConvertSeasons(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand allSeriesCmd = conn.CreateCommand())
{
allSeriesCmd.Transaction = tran;
allSeriesCmd.CommandText = @"SELECT Id FROM Series";
using (IDataReader allSeriesReader = allSeriesCmd.ExecuteReader())
{
while (allSeriesReader.Read())
{
int seriesId = allSeriesReader.GetInt32(0);
var seasons = new List<dynamic>();
using (IDbCommand seasonsCmd = conn.CreateCommand())
{
seasonsCmd.Transaction = tran;
seasonsCmd.CommandText = String.Format(@"SELECT SeasonNumber, Monitored FROM Seasons WHERE SeriesId = {0}", seriesId);
using (IDataReader seasonReader = seasonsCmd.ExecuteReader())
{
while (seasonReader.Read())
{
int seasonNumber = seasonReader.GetInt32(0);
bool monitored = seasonReader.GetBoolean(1);
if (seasonNumber == 0)
{
monitored = false;
}
seasons.Add(new { seasonNumber, monitored });
}
}
}
using (IDbCommand updateCmd = conn.CreateCommand())
{
var text = String.Format("UPDATE Series SET Seasons = '{0}' WHERE Id = {1}", seasons.ToJson(), seriesId);
updateCmd.Transaction = tran;
updateCmd.CommandText = text;
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Data;
using FluentMigrator;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(23)]
public class add_config_contract_to_indexers : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Update.Table("Indexers").Set(new { ConfigContract = "NewznabSettings" }).Where(new { Implementation = "Newznab" });
Update.Table("Indexers").Set(new { ConfigContract = "OmgwtfnzbsSettings" }).Where(new { Implementation = "Omgwtfnzbs" });
Update.Table("Indexers").Set(new { ConfigContract = "NullConfig" }).Where(new { Implementation = "Wombles" });
Update.Table("Indexers").Set(new { ConfigContract = "NullConfig" }).Where(new { Implementation = "Eztv" });
Delete.FromTable("Indexers").IsNull("ConfigContract");
}
}
}

View File

@ -3,6 +3,7 @@
using System.Linq;
using Marr.Data;
using Marr.Data.Mapping;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore.Converters;
@ -15,6 +16,7 @@
using NzbDrone.Core.Qualities;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.SeriesStats;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Datastore
@ -69,6 +71,7 @@ public static void Map()
private static void RegisterMappers()
{
RegisterEmbeddedConverter();
RegisterProviderSettingConverter();
MapRepository.Instance.RegisterTypeConverter(typeof(Int32), new Int32Converter());
MapRepository.Instance.RegisterTypeConverter(typeof(DateTime), new UtcConverter());
@ -78,10 +81,20 @@ private static void RegisterMappers()
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
}
private static void RegisterProviderSettingConverter()
{
var settingTypes = typeof(IProviderConfig).Assembly.ImplementationsOf<IProviderConfig>();
var providerSettingConverter = new ProviderSettingConverter();
foreach (var embeddedType in settingTypes)
{
MapRepository.Instance.RegisterTypeConverter(embeddedType, providerSettingConverter);
}
}
private static void RegisterEmbeddedConverter()
{
var embeddedTypes = typeof(IEmbeddedDocument).Assembly.GetTypes()
.Where(c => c.GetInterfaces().Any(i => i == typeof(IEmbeddedDocument)));
var embeddedTypes = typeof(IEmbeddedDocument).Assembly.ImplementationsOf<IEmbeddedDocument>();
var embeddedConvertor = new EmbeddedDocumentConverter();

View File

@ -24,7 +24,7 @@ public interface ISearchForNzb
public class NzbSearchService : ISearchForNzb
{
private readonly IIndexerService _indexerService;
private readonly IIndexerFactory _indexerFactory;
private readonly IFetchFeedFromIndexers _feedFetcher;
private readonly ISceneMappingService _sceneMapping;
private readonly ISeriesService _seriesService;
@ -32,7 +32,7 @@ public class NzbSearchService : ISearchForNzb
private readonly IMakeDownloadDecision _makeDownloadDecision;
private readonly Logger _logger;
public NzbSearchService(IIndexerService indexerService,
public NzbSearchService(IIndexerFactory indexerFactory,
IFetchFeedFromIndexers feedFetcher,
ISceneMappingService sceneMapping,
ISeriesService seriesService,
@ -40,7 +40,7 @@ public NzbSearchService(IIndexerService indexerService,
IMakeDownloadDecision makeDownloadDecision,
Logger logger)
{
_indexerService = indexerService;
_indexerFactory = indexerFactory;
_feedFetcher = feedFetcher;
_sceneMapping = sceneMapping;
_seriesService = seriesService;
@ -132,7 +132,7 @@ public List<DownloadDecision> SeasonSearch(int seriesId, int seasonNumber)
private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReleaseInfo>> searchAction, SearchCriteriaBase criteriaBase)
{
var indexers = _indexerService.GetAvailableIndexers().ToList();
var indexers = _indexerFactory.GetAvailableProviders().ToList();
var reports = new List<ReleaseInfo>();
_logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase);

View File

@ -1,28 +1,19 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Eztv
{
public class Eztv : IndexerBase
public class Eztv : IndexerBase<NullConfig>
{
public override string Name
{
get { return "Eztv"; }
}
public override IndexerKind Kind
public override DownloadProtocol Protocol
{
get
{
return IndexerKind.Torrent;
return DownloadProtocol.Torrent;
}
}
public override bool EnableByDefault
{
get { return false; }
}
public override IParseFeed Parser
{
get
@ -35,10 +26,7 @@ public override IEnumerable<string> RecentFeed
{
get
{
return new[]
{
"http://www.ezrss.it/feed/"
};
yield return "http://www.ezrss.it/feed/";
}
}
@ -55,7 +43,7 @@ public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int
public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date)
{
//EZTV doesn't support searching based on actual epidose airdate. they only support release date.
//EZTV doesn't support searching based on actual episode airdate. they only support release date.
return new string[0];
}
}

View File

@ -14,13 +14,13 @@ public interface IFetchAndParseRss
public class FetchAndParseRssService : IFetchAndParseRss
{
private readonly IIndexerService _indexerService;
private readonly IIndexerFactory _indexerFactory;
private readonly IFetchFeedFromIndexers _feedFetcher;
private readonly Logger _logger;
public FetchAndParseRssService(IIndexerService indexerService, IFetchFeedFromIndexers feedFetcher, Logger logger)
public FetchAndParseRssService(IIndexerFactory indexerFactory, IFetchFeedFromIndexers feedFetcher, Logger logger)
{
_indexerService = indexerService;
_indexerFactory = indexerFactory;
_feedFetcher = feedFetcher;
_logger = logger;
}
@ -29,7 +29,7 @@ public List<ReleaseInfo> Fetch()
{
var result = new List<ReleaseInfo>();
var indexers = _indexerService.GetAvailableIndexers().ToList();
var indexers = _indexerFactory.GetAvailableProviders().ToList();
if (!indexers.Any())
{

View File

@ -1,23 +1,15 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public interface IIndexer
public interface IIndexer : IProvider
{
string Name { get; }
bool EnableByDefault { get; }
IEnumerable<IndexerDefinition> DefaultDefinitions { get; }
IndexerDefinition InstanceDefinition { get; set; }
IParseFeed Parser { get; }
DownloadProtocol Protocol { get; }
IEnumerable<string> RecentFeed { get; }
IParseFeed Parser { get; }
IndexerKind Kind { get; }
IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber);
IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date);
IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset);

View File

@ -1,41 +1,62 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public abstract class IndexerBase : IIndexer
public abstract class IndexerBase<TSettings> : IIndexer where TSettings : IProviderConfig, new()
{
public abstract string Name { get; }
public abstract IndexerKind Kind { get; }
public virtual bool EnableByDefault { get { return true; } }
public IndexerDefinition InstanceDefinition { get; set; }
public virtual IEnumerable<IndexerDefinition> DefaultDefinitions
public Type ConfigContract
{
get
{
return typeof(TSettings);
}
}
public virtual IEnumerable<ProviderDefinition> DefaultDefinitions
{
get
{
var config = (IProviderConfig)new TSettings();
yield return new IndexerDefinition
{
Name = Name,
Enable = EnableByDefault,
Name = GetType().Name,
Enable = config.Validate().IsValid,
Implementation = GetType().Name,
Settings = String.Empty
Settings = config
};
}
}
public ProviderDefinition Definition { get; set; }
public abstract DownloadProtocol Protocol { get; }
protected TSettings Settings
{
get
{
return (TSettings)Definition.Settings;
}
}
public virtual IParseFeed Parser { get; private set; }
public abstract IEnumerable<string> RecentFeed { get; }
public abstract IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber);
public abstract IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date);
public abstract IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset);
public override string ToString()
{
return GetType().Name;
}
}
public enum IndexerKind
public enum DownloadProtocol
{
Usenet,
Torrent

View File

@ -1,13 +1,8 @@
using System;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public class IndexerDefinition : ModelBase
public class IndexerDefinition : ProviderDefinition
{
public Boolean Enable { get; set; }
public String Name { get; set; }
public String Settings { get; set; }
public String Implementation { get; set; }
}
}

View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public interface IIndexerFactory : IProviderFactory<IIndexer, IndexerDefinition>
{
}
public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, IIndexerFactory
{
private readonly IIndexerRepository _providerRepository;
private readonly IEnumerable<IIndexer> _providers;
public IndexerFactory(IIndexerRepository providerRepository, IEnumerable<IIndexer> providers, Logger logger)
: base(providerRepository, providers, logger)
{
_providerRepository = providerRepository;
_providers = providers;
}
protected override void InitializeProviders()
{
var definitions = _providers.Where(c => c.Protocol == DownloadProtocol.Usenet)
.SelectMany(indexer => indexer.DefaultDefinitions);
var currentProviders = All();
var newProviders = definitions.Where(def => currentProviders.All(c => c.Implementation != def.Implementation)).ToList();
if (newProviders.Any())
{
_providerRepository.InsertMany(newProviders.Cast<IndexerDefinition>().ToList());
}
}
}
}

View File

@ -33,11 +33,11 @@ public FetchFeedService(IHttpProvider httpProvider, Logger logger)
public virtual IList<ReleaseInfo> FetchRss(IIndexer indexer)
{
_logger.Debug("Fetching feeds from " + indexer.Name);
_logger.Debug("Fetching feeds from " + indexer);
var result = Fetch(indexer, indexer.RecentFeed);
_logger.Debug("Finished processing feeds from " + indexer.Name);
_logger.Debug("Finished processing feeds from " + indexer);
return result;
}
@ -48,7 +48,7 @@ public IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCri
var result = Fetch(indexer, searchCriteria, 0).DistinctBy(c => c.DownloadUrl).ToList();
_logger.Info("Finished searching {0} for {1}. Found {2}", indexer.Name, searchCriteria, result.Count);
_logger.Info("Finished searching {0} for {1}. Found {2}", indexer, searchCriteria, result.Count);
return result;
}
@ -61,7 +61,7 @@ private IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCr
var result = Fetch(indexer, searchUrls);
_logger.Info("{0} offset {1}. Found {2}", indexer.Name, searchCriteria, result.Count);
_logger.Info("{0} offset {1}. Found {2}", indexer, searchCriteria, result.Count);
if (result.Count > 90)
{
@ -79,7 +79,7 @@ public IList<ReleaseInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria se
var result = Fetch(indexer, searchUrls);
_logger.Info("Finished searching {0} for {1}. Found {2}", indexer.Name, searchCriteria, result.Count);
_logger.Info("Finished searching {0} for {1}. Found {2}", indexer, searchCriteria, result.Count);
return result;
}
@ -90,7 +90,7 @@ public IList<ReleaseInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria sea
var searchUrls = indexer.GetDailyEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.Airtime);
var result = Fetch(indexer, searchUrls);
_logger.Info("Finished searching {0} for {1}. Found {2}", indexer.Name, searchCriteria, result.Count);
_logger.Info("Finished searching {0} for {1}. Found {2}", indexer, searchCriteria, result.Count);
return result;
}
@ -119,17 +119,16 @@ private List<ReleaseInfo> Fetch(IIndexer indexer, IEnumerable<string> urls)
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
webException.Message.Contains("timed out"))
{
_logger.Warn("{0} server is currently unavailable. {1} {2}", indexer.Name, url,
webException.Message);
_logger.Warn("{0} server is currently unavailable. {1} {2}", indexer, url, webException.Message);
}
else
{
_logger.Warn("{0} {1} {2}", indexer.Name, url, webException.Message);
_logger.Warn("{0} {1} {2}", indexer, url, webException.Message);
}
}
catch (ApiKeyException)
{
_logger.Warn("Invalid API Key for {0} {1}", indexer.Name, url);
_logger.Warn("Invalid API Key for {0} {1}", indexer, url);
}
catch (Exception feedEx)
{
@ -138,7 +137,7 @@ private List<ReleaseInfo> Fetch(IIndexer indexer, IEnumerable<string> urls)
}
}
result.ForEach(c => c.Indexer = indexer.Name);
result.ForEach(c => c.Indexer = indexer.Definition.Name);
return result;
}

View File

@ -1,33 +1,20 @@
using System;
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public interface IIndexerRepository : IBasicRepository<IndexerDefinition>
public interface IIndexerRepository : IProviderRepository<IndexerDefinition>
{
IndexerDefinition Get(string name);
IndexerDefinition Find(string name);
}
public class IndexerRepository : BasicRepository<IndexerDefinition>, IIndexerRepository
public class IndexerRepository : ProviderRepository<IndexerDefinition>, IIndexerRepository
{
public IndexerRepository(IDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
public IndexerDefinition Get(string name)
{
return Query.Single(i => i.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
}
public IndexerDefinition Find(string name)
{
return Query.SingleOrDefault(i => i.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
}
}
}

View File

@ -1,201 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
using Omu.ValueInjecter;
namespace NzbDrone.Core.Indexers
{
public class Indexer
{
public int Id { get; set; }
public string Name { get; set; }
public bool Enable { get; set; }
public IIndexerSetting Settings { get; set; }
public IIndexer Instance { get; set; }
public string Implementation { get; set; }
}
public interface IIndexerService
{
List<Indexer> All();
List<IIndexer> GetAvailableIndexers();
Indexer Get(int id);
Indexer Get(string name);
List<Indexer> Schema();
Indexer Create(Indexer indexer);
Indexer Update(Indexer indexer);
void Delete(int id);
}
public class IndexerService : IIndexerService, IHandle<ApplicationStartedEvent>
{
private readonly IIndexerRepository _indexerRepository;
private readonly IConfigFileProvider _configFileProvider;
private readonly INewznabTestService _newznabTestService;
private readonly Logger _logger;
private readonly List<IIndexer> _indexers;
public IndexerService(IIndexerRepository indexerRepository,
IEnumerable<IIndexer> indexers,
IConfigFileProvider configFileProvider,
INewznabTestService newznabTestService,
Logger logger)
{
_indexerRepository = indexerRepository;
_configFileProvider = configFileProvider;
_newznabTestService = newznabTestService;
_logger = logger;
if (!configFileProvider.Torrent)
{
_indexers = indexers.Where(c => c.Kind != IndexerKind.Torrent).ToList();
}
else
{
_indexers = indexers.ToList();
}
}
public List<Indexer> All()
{
return _indexerRepository.All().Select(ToIndexer).ToList();
}
public List<IIndexer> GetAvailableIndexers()
{
return All().Where(c => c.Enable && c.Settings.Validate().IsValid).Select(c => c.Instance).ToList();
}
public Indexer Get(int id)
{
return ToIndexer(_indexerRepository.Get(id));
}
public Indexer Get(string name)
{
return ToIndexer(_indexerRepository.Get(name));
}
public List<Indexer> Schema()
{
var indexers = new List<Indexer>();
var newznab = new Indexer();
newznab.Instance = new Newznab.Newznab();
newznab.Id = 1;
newznab.Name = "Newznab";
newznab.Settings = new NewznabSettings();
newznab.Implementation = "Newznab";
indexers.Add(newznab);
return indexers;
}
public Indexer Create(Indexer indexer)
{
var definition = new IndexerDefinition
{
Name = indexer.Name,
Enable = indexer.Enable,
Implementation = indexer.Implementation,
Settings = indexer.Settings.ToJson()
};
var instance = ToIndexer(definition).Instance;
_newznabTestService.Test(instance);
definition = _indexerRepository.Insert(definition);
indexer.Id = definition.Id;
return indexer;
}
public Indexer Update(Indexer indexer)
{
var definition = _indexerRepository.Get(indexer.Id);
definition.InjectFrom(indexer);
definition.Settings = indexer.Settings.ToJson();
_indexerRepository.Update(definition);
return indexer;
}
public void Delete(int id)
{
_indexerRepository.Delete(id);
}
private Indexer ToIndexer(IndexerDefinition definition)
{
var indexer = new Indexer();
indexer.Id = definition.Id;
indexer.Enable = definition.Enable;
indexer.Instance = GetInstance(definition);
indexer.Name = definition.Name;
indexer.Implementation = definition.Implementation;
if (indexer.Instance.GetType().GetMethod("ImportSettingsFromJson") != null)
{
indexer.Settings = ((dynamic)indexer.Instance).ImportSettingsFromJson(definition.Settings);
}
else
{
indexer.Settings = NullSetting.Instance;
}
return indexer;
}
private IIndexer GetInstance(IndexerDefinition indexerDefinition)
{
var type = GetImplementation(indexerDefinition);
var instance = (IIndexer)Activator.CreateInstance(type);
instance.InstanceDefinition = indexerDefinition;
return instance;
}
private Type GetImplementation(IndexerDefinition indexerDefinition)
{
return _indexers.Select(c => c.GetType()).SingleOrDefault(c => c.Name.Equals(indexerDefinition.Implementation, StringComparison.InvariantCultureIgnoreCase));
}
public void Handle(ApplicationStartedEvent message)
{
_logger.Debug("Initializing indexers. Count {0}", _indexers.Count);
RemoveMissingImplementations();
var definitions = _indexers.SelectMany(indexer => indexer.DefaultDefinitions);
var currentIndexer = All();
var newIndexers = definitions.Where(def => currentIndexer.All(c => c.Implementation != def.Implementation)).ToList();
if (newIndexers.Any())
{
_indexerRepository.InsertMany(newIndexers);
}
}
private void RemoveMissingImplementations()
{
var storedIndexers = _indexerRepository.All();
foreach (var indexerDefinition in storedIndexers.Where(i => GetImplementation(i) == null))
{
_logger.Debug("Removing Indexer {0} ", indexerDefinition.Name);
_indexerRepository.Delete(indexerDefinition);
}
}
}
}

View File

@ -1,31 +0,0 @@
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Indexers
{
public interface IProviderIndexerSetting
{
TSetting Get<TSetting>(IIndexer indexer) where TSetting : IIndexerSetting, new();
}
public class IndexerSettingProvider : IProviderIndexerSetting
{
private readonly IIndexerRepository _indexerRepository;
public IndexerSettingProvider(IIndexerRepository indexerRepository)
{
_indexerRepository = indexerRepository;
}
public TSetting Get<TSetting>(IIndexer indexer) where TSetting : IIndexerSetting, new()
{
var indexerDef = _indexerRepository.Find(indexer.Name);
if (indexerDef == null || string.IsNullOrWhiteSpace(indexerDef.Settings))
{
return new TSetting();
}
return Json.Deserialize<TSetting>(indexerDef.Settings);
}
}
}

View File

@ -1,13 +1,14 @@
using NzbDrone.Common.Messaging;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public class IndexerSettingUpdatedEvent : IEvent
{
public string IndexerName { get; private set; }
public IIndexerSetting IndexerSetting { get; private set; }
public IProviderConfig IndexerSetting { get; private set; }
public IndexerSettingUpdatedEvent(string indexerName, IIndexerSetting indexerSetting)
public IndexerSettingUpdatedEvent(string indexerName, IProviderConfig indexerSetting)
{
IndexerName = indexerName;
IndexerSetting = indexerSetting;

View File

@ -1,21 +0,0 @@
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Indexers
{
public abstract class IndexerWithSetting<TSetting> : IndexerBase where TSetting : class, IIndexerSetting, new()
{
public TSetting Settings { get; set; }
public override bool EnableByDefault
{
get { return false; }
}
public TSetting ImportSettingsFromJson(string json)
{
Settings = Json.Deserialize<TSetting>(json) ?? new TSetting();
return Settings;
}
}
}

View File

@ -2,10 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Newznab
{
public class Newznab : IndexerWithSetting<NewznabSettings>
public class Newznab : IndexerBase<NewznabSettings>
{
public override IParseFeed Parser
{
@ -15,7 +16,7 @@ public override IParseFeed Parser
}
}
public override IEnumerable<IndexerDefinition> DefaultDefinitions
public override IEnumerable<ProviderDefinition> DefaultDefinitions
{
get
{
@ -51,7 +52,7 @@ public override IEnumerable<IndexerDefinition> DefaultDefinitions
}
}
private string GetSettings(string url, List<int> categories)
private NewznabSettings GetSettings(string url, List<int> categories)
{
var settings = new NewznabSettings { Url = url };
@ -60,7 +61,7 @@ private string GetSettings(string url, List<int> categories)
settings.Categories = categories;
}
return settings.ToJson();
return settings;
}
public override IEnumerable<string> RecentFeed
@ -68,7 +69,7 @@ public override IEnumerable<string> RecentFeed
get
{
//Todo: We should be able to update settings on start
if (Name.Equals("nzbs.org", StringComparison.InvariantCultureIgnoreCase))
if (Settings.Url.Contains("nzbs.org"))
{
Settings.Categories = new List<int> { 5000 };
}
@ -114,19 +115,11 @@ public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int
return RecentFeed.Select(url => String.Format("{0}&limit=100&q={1}&season={2}&offset={3}", url, NewsnabifyTitle(seriesTitle), seasonNumber, offset));
}
public override string Name
public override DownloadProtocol Protocol
{
get
{
return InstanceDefinition.Name;
}
}
public override IndexerKind Kind
{
get
{
return IndexerKind.Usenet;
return DownloadProtocol.Usenet;
}
}
@ -135,4 +128,4 @@ private static string NewsnabifyTitle(string title)
return title.Replace("+", "%20");
}
}
}
}

View File

@ -3,6 +3,7 @@
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Newznab
@ -16,7 +17,7 @@ public NewznabSettingsValidator()
}
public class NewznabSettings : IIndexerSetting
public class NewznabSettings : IProviderConfig
{
private static readonly NewznabSettingsValidator Validator = new NewznabSettingsValidator();

View File

@ -3,18 +3,13 @@
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
{
public class Omgwtfnzbs : IndexerWithSetting<OmgwtfnzbsSettings>
public class Omgwtfnzbs : IndexerBase<OmgwtfnzbsSettings>
{
public override string Name
{
get { return "omgwtfnzbs"; }
}
public override IndexerKind Kind
public override DownloadProtocol Protocol
{
get
{
return IndexerKind.Usenet;
return DownloadProtocol.Usenet;
}
}

View File

@ -2,6 +2,7 @@
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
{
@ -14,7 +15,7 @@ public OmgwtfnzbsSettingsValidator()
}
}
public class OmgwtfnzbsSettings : IIndexerSetting
public class OmgwtfnzbsSettings : IProviderConfig
{
private static readonly OmgwtfnzbsSettingsValidator Validator = new OmgwtfnzbsSettingsValidator();

View File

@ -1,20 +1,16 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Wombles
{
public class Wombles : IndexerBase
public class Wombles : IndexerBase<NullConfig>
{
public override string Name
{
get { return "WomblesIndex"; }
}
public override IndexerKind Kind
public override DownloadProtocol Protocol
{
get
{
return IndexerKind.Usenet;
return DownloadProtocol.Usenet;
}
}

View File

@ -1,9 +1,11 @@
using System;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications.Email
{
public class EmailSettings : INotifcationSettings
public class EmailSettings : IProviderConfig
{
public EmailSettings()
{
@ -38,5 +40,10 @@ public bool IsValid
return !string.IsNullOrWhiteSpace(Server) && Port > 0 && !string.IsNullOrWhiteSpace(From) && !string.IsNullOrWhiteSpace(To);
}
}
public ValidationResult Validate()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,9 +1,11 @@
using System;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications.Growl
{
public class GrowlSettings : INotifcationSettings
public class GrowlSettings : IProviderConfig
{
public GrowlSettings()
{
@ -26,5 +28,10 @@ public bool IsValid
return !string.IsNullOrWhiteSpace(Host) && !string.IsNullOrWhiteSpace(Password) && Port > 0;
}
}
public ValidationResult Validate()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,7 +0,0 @@
namespace NzbDrone.Core.Notifications
{
public interface INotifcationSettings
{
bool IsValid { get; }
}
}

View File

@ -1,4 +1,6 @@
namespace NzbDrone.Core.Notifications
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications
{
public class Notification
{
@ -8,7 +10,7 @@ public class Notification
public string Link { get; set; }
public bool OnGrab { get; set; }
public bool OnDownload { get; set; }
public INotifcationSettings Settings { get; set; }
public IProviderConfig Settings { get; set; }
public INotification Instance { get; set; }
public string Implementation { get; set; }
}

View File

@ -1,9 +1,10 @@
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Notifications
{
public abstract class NotificationBase<TSetting> : INotification where TSetting : class, INotifcationSettings, new()
public abstract class NotificationBase<TSetting> : INotification where TSetting : class, IProviderConfig, new()
{
public abstract string Name { get; }
public abstract string ImplementationName { get; }

View File

@ -1,5 +1,6 @@
using System;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications
{
@ -11,4 +12,11 @@ public class NotificationDefinition : ModelBase
public String Settings { get; set; }
public String Implementation { get; set; }
}
public class NotificationProviderModel : ProviderDefinition
{
public Boolean OnGrab { get; set; }
public Boolean OnDownload { get; set; }
}
}

View File

@ -7,6 +7,7 @@
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
using Omu.ValueInjecter;
@ -18,7 +19,7 @@ public interface INotificationService
Notification Get(int id);
List<Notification> Schema();
Notification Create(Notification notification);
Notification Update(Notification notification);
void Update(Notification notification);
void Delete(int id);
}
@ -71,7 +72,7 @@ public List<Notification> Schema()
var instanceType = newNotification.Instance.GetType();
var baseGenArgs = instanceType.BaseType.GetGenericArguments();
newNotification.Settings = (INotifcationSettings)Activator.CreateInstance(baseGenArgs[0]);
newNotification.Settings = (IProviderConfig)Activator.CreateInstance(baseGenArgs[0]);
newNotification.Implementation = type.Name;
notifications.Add(newNotification);
@ -93,15 +94,13 @@ public Notification Create(Notification notification)
return notification;
}
public Notification Update(Notification notification)
public void Update(Notification notification)
{
var definition = _notificationRepository.Get(notification.Id);
definition.InjectFrom(notification);
definition.Settings = notification.Settings.ToJson();
_notificationRepository.Update(definition);
return notification;
}
public void Delete(int id)

View File

@ -1,10 +1,11 @@
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications
{
public interface INotificationSettingsProvider
{
TSetting Get<TSetting>(INotification indexer) where TSetting : INotifcationSettings, new();
TSetting Get<TSetting>(INotification indexer) where TSetting : IProviderConfig, new();
}
public class NotificationSettingsProvider : INotificationSettingsProvider
@ -16,7 +17,7 @@ public NotificationSettingsProvider(INotificationRepository notificationReposito
_notificationRepository = notificationRepository;
}
public TSetting Get<TSetting>(INotification indexer) where TSetting : INotifcationSettings, new()
public TSetting Get<TSetting>(INotification indexer) where TSetting : IProviderConfig, new()
{
var indexerDef = _notificationRepository.Find(indexer.Name);

View File

@ -1,9 +1,11 @@
using System;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications.NotifyMyAndroid
{
public class NotifyMyAndroidSettings : INotifcationSettings
public class NotifyMyAndroidSettings : IProviderConfig
{
[FieldDefinition(0, Label = "API Key", HelpLink = "http://www.notifymyandroid.com/")]
public String ApiKey { get; set; }
@ -18,5 +20,10 @@ public bool IsValid
return !String.IsNullOrWhiteSpace(ApiKey) && Priority != null & Priority >= -1 && Priority <= 2;
}
}
public ValidationResult Validate()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,9 +1,11 @@
using System;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications.Plex
{
public class PlexClientSettings : INotifcationSettings
public class PlexClientSettings : IProviderConfig
{
public PlexClientSettings()
{
@ -29,5 +31,10 @@ public bool IsValid
return !string.IsNullOrWhiteSpace(Host);
}
}
public ValidationResult Validate()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,9 +1,11 @@
using System;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications.Plex
{
public class PlexServerSettings : INotifcationSettings
public class PlexServerSettings : IProviderConfig
{
public PlexServerSettings()
{
@ -26,5 +28,10 @@ public bool IsValid
return !string.IsNullOrWhiteSpace(Host);
}
}
public ValidationResult Validate()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,9 +1,11 @@
using System;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications.Prowl
{
public class ProwlSettings : INotifcationSettings
public class ProwlSettings : IProviderConfig
{
[FieldDefinition(0, Label = "API Key", HelpLink = "https://www.prowlapp.com/api_settings.php")]
public String ApiKey { get; set; }
@ -18,5 +20,10 @@ public bool IsValid
return !string.IsNullOrWhiteSpace(ApiKey) && Priority != null & Priority >= -2 && Priority <= 2;
}
}
public ValidationResult Validate()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,9 +1,11 @@
using System;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications.PushBullet
{
public class PushBulletSettings : INotifcationSettings
public class PushBulletSettings : IProviderConfig
{
[FieldDefinition(0, Label = "API Key", HelpLink = "https://www.pushbullet.com/")]
public String ApiKey { get; set; }
@ -18,5 +20,10 @@ public bool IsValid
return !String.IsNullOrWhiteSpace(ApiKey) && DeviceId > 0;
}
}
public ValidationResult Validate()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,9 +1,11 @@
using System;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications.Pushover
{
public class PushoverSettings : INotifcationSettings
public class PushoverSettings : IProviderConfig
{
[FieldDefinition(0, Label = "User Key", HelpLink = "https://pushover.net/")]
public String UserKey { get; set; }
@ -18,5 +20,10 @@ public bool IsValid
return !string.IsNullOrWhiteSpace(UserKey) && Priority != null & Priority >= -1 && Priority <= 2;
}
}
public ValidationResult Validate()
{
throw new NotImplementedException();
}
}
}

View File

@ -1,11 +1,13 @@
using System;
using System.ComponentModel;
using FluentValidation.Results;
using Newtonsoft.Json;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Notifications.Xbmc
{
public class XbmcSettings : INotifcationSettings
public class XbmcSettings : IProviderConfig
{
public XbmcSettings()
{
@ -51,5 +53,10 @@ public bool IsValid
return !string.IsNullOrWhiteSpace(Host) && Port > 0;
}
}
public ValidationResult Validate()
{
throw new NotImplementedException();
}
}
}

View File

@ -141,6 +141,7 @@
<Compile Include="DataAugmentation\Xem\XemService.cs" />
<Compile Include="Datastore\ConnectionStringFactory.cs" />
<Compile Include="Datastore\Converters\BooleanIntConverter.cs" />
<Compile Include="Datastore\Converters\ProviderSettingConverter.cs" />
<Compile Include="Datastore\Converters\QualityIntConverter.cs" />
<Compile Include="Datastore\Converters\Int32Converter.cs" />
<Compile Include="Datastore\Converters\EmbeddedDocumentConverter.cs" />
@ -172,6 +173,8 @@
<Compile Include="Datastore\Migration\019_restore_unique_constraints.cs" />
<Compile Include="Datastore\Migration\020_add_year_and_seasons_to_series.cs" />
<Compile Include="Datastore\Migration\021_drop_seasons_table.cs" />
<Compile Include="Datastore\Migration\022_move_notification_to_generic_provider.cs" />
<Compile Include="Datastore\Migration\023_add_config_contract_to_indexers.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
@ -241,12 +244,10 @@
<Compile Include="Indexers\IIndexer.cs" />
<Compile Include="Indexers\IndexerSettingUpdatedEvent.cs" />
<Compile Include="Indexers\NewznabTestService.cs" />
<Compile Include="Indexers\IndexerWithSetting.cs" />
<Compile Include="Indexers\IParseFeed.cs" />
<Compile Include="Indexers\Newznab\NewznabException.cs" />
<Compile Include="Indexers\Newznab\NewznabPreProcessor.cs" />
<Compile Include="Indexers\Newznab\SizeParsingException.cs" />
<Compile Include="Indexers\NullSetting.cs" />
<Compile Include="Indexers\RssSyncCommand.cs" />
<Compile Include="Indexers\XElementExtensions.cs" />
<Compile Include="Instrumentation\Commands\ClearLogCommand.cs" />
@ -343,13 +344,11 @@
<Compile Include="Indexers\IndexerBase.cs" />
<Compile Include="Indexers\IndexerDefinition.cs" />
<Compile Include="Indexers\IndexerRepository.cs" />
<Compile Include="Indexers\IndexerSettingProvider.cs" />
<Compile Include="Indexers\Newznab\Newznab.cs" />
<Compile Include="Indexers\Newznab\NewznabSettings.cs" />
<Compile Include="Indexers\Newznab\NewznabParser.cs" />
<Compile Include="Indexers\Omgwtfnzbs\Omgwtfnzbs.cs" />
<Compile Include="Indexers\Omgwtfnzbs\OmgwtfnzbsParser.cs" />
<Compile Include="Indexers\IIndexerSetting.cs" />
<Compile Include="Indexers\Omgwtfnzbs\OmgwtfnzbsSettings.cs" />
<Compile Include="Indexers\Wombles\Wombles.cs" />
<Compile Include="Indexers\Wombles\WomblesParser.cs" />
@ -371,7 +370,6 @@
<Compile Include="MetadataSource\Trakt\Images.cs" />
<Compile Include="MetadataSource\Trakt\Season.cs" />
<Compile Include="MetadataSource\Trakt\FullShow.cs" />
<Compile Include="Notifications\INotifcationSettings.cs" />
<Compile Include="Notifications\Plex\TestPlexServerCommand.cs" />
<Compile Include="Notifications\Plex\PlexServer.cs" />
<Compile Include="Notifications\Plex\PlexClientSettings.cs" />
@ -417,6 +415,8 @@
<Compile Include="Parser\Parser.cs" />
<Compile Include="Parser\ParsingService.cs" />
<Compile Include="Parser\QualityParser.cs" />
<Compile Include="ThingiProvider\ConfigContractNotFoundException.cs" />
<Compile Include="ThingiProvider\IProvider.cs" />
<Compile Include="Qualities\QualityProfileInUseException.cs" />
<Compile Include="Qualities\QualitySizeRepository.cs" />
<Compile Include="Qualities\QualityProfileRepository.cs" />
@ -427,6 +427,13 @@
<Compile Include="Rest\RestSharpExtensions.cs" />
<Compile Include="Rest\RestException.cs" />
<Compile Include="SeriesStats\SeriesStatisticsService.cs" />
<Compile Include="ThingiProvider\IProviderConfig.cs" />
<Compile Include="ThingiProvider\IProviderFactory.cs" />
<Compile Include="ThingiProvider\IProviderRepository.cs" />
<Compile Include="ThingiProvider\NullConfig.cs" />
<Compile Include="ThingiProvider\ProviderDefinition.cs" />
<Compile Include="ThingiProvider\ProviderRepository.cs" />
<Compile Include="ThingiProvider\ProviderFactory.cs" />
<Compile Include="Tv\EpisodeService.cs" />
<Compile Include="Tv\Events\EpisodeInfoDeletedEvent.cs" />
<Compile Include="Tv\Events\EpisodeInfoUpdatedEvent.cs" />
@ -505,7 +512,7 @@
<Compile Include="History\HistoryService.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Indexers\IndexerService.cs">
<Compile Include="Indexers\IndexerFactory.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Indexers\IndexerFetchService.cs">

View File

@ -13,7 +13,7 @@
<AnalyseExecutionTimes>true</AnalyseExecutionTimes>
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
<DefaultTestTimeout>5000</DefaultTestTimeout>
<UseBuildConfiguration>Debug</UseBuildConfiguration>
<UseBuildConfiguration>RELEASE</UseBuildConfiguration>
<UseBuildPlatform>x86</UseBuildPlatform>
<ProxyProcessPath></ProxyProcessPath>
<UseCPUArchitecture>x86</UseCPUArchitecture>

View File

@ -0,0 +1,13 @@
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.ThingiProvider
{
public class ConfigContractNotFoundException : NzbDroneException
{
public ConfigContractNotFoundException(string contract)
: base("Couldn't find config contract " + contract)
{
}
}
}

View File

@ -0,0 +1,14 @@

using System;
using System.Collections.Generic;
namespace NzbDrone.Core.ThingiProvider
{
public interface IProvider
{
Type ConfigContract { get; }
IEnumerable<ProviderDefinition> DefaultDefinitions { get; }
ProviderDefinition Definition { get; set; }
}
}

View File

@ -1,9 +1,9 @@
using FluentValidation.Results;
namespace NzbDrone.Core.Indexers
namespace NzbDrone.Core.ThingiProvider
{
public interface IIndexerSetting
public interface IProviderConfig
{
ValidationResult Validate();
}
}
}

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace NzbDrone.Core.ThingiProvider
{
public interface IProviderFactory<TProvider, TProviderDefinition>
where TProviderDefinition : ProviderDefinition, new()
where TProvider : IProvider
{
List<TProviderDefinition> All();
List<TProvider> GetAvailableProviders();
TProviderDefinition Get(int id);
TProviderDefinition Create(TProviderDefinition indexer);
void Update(TProviderDefinition indexer);
void Delete(int id);
List<TProviderDefinition> Templates();
}
}

View File

@ -0,0 +1,8 @@
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.ThingiProvider
{
public interface IProviderRepository<TProvider> : IBasicRepository<TProvider> where TProvider : ModelBase, new()
{
}
}

View File

@ -1,10 +1,10 @@
using FluentValidation.Results;
namespace NzbDrone.Core.Indexers
namespace NzbDrone.Core.ThingiProvider
{
public class NullSetting : IIndexerSetting
public class NullConfig : IProviderConfig
{
public static readonly NullSetting Instance = new NullSetting();
public static readonly NullConfig Instance = new NullConfig();
public ValidationResult Validate()
{

View File

@ -0,0 +1,30 @@
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.ThingiProvider
{
public abstract class ProviderDefinition : ModelBase
{
private IProviderConfig _settings;
public string Name { get; set; }
public string Implementation { get; set; }
public bool Enable { get; set; }
public string ConfigContract { get; set; }
public IProviderConfig Settings
{
get
{
return _settings;
}
set
{
_settings = value;
if (value != null)
{
ConfigContract = value.GetType().Name;
}
}
}
}
}

View File

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.ThingiProvider
{
public abstract class ProviderFactory<TProvider, TProviderDefinition> : IProviderFactory<TProvider, TProviderDefinition>, IHandle<ApplicationStartedEvent>
where TProviderDefinition : ProviderDefinition, new()
where TProvider : IProvider
{
private readonly IProviderRepository<TProviderDefinition> _providerRepository;
private readonly Logger _logger;
private readonly List<TProvider> _providers;
protected ProviderFactory(IProviderRepository<TProviderDefinition> providerRepository, IEnumerable<TProvider> providers, Logger logger)
{
_providerRepository = providerRepository;
_providers = providers.ToList();
_logger = logger;
}
public List<TProviderDefinition> All()
{
return _providerRepository.All().ToList();
}
public List<TProviderDefinition> Templates()
{
return _providers.Select(p => new TProviderDefinition()
{
ConfigContract = p.ConfigContract.Name,
Implementation = p.GetType().Name,
Settings = (IProviderConfig)Activator.CreateInstance(p.ConfigContract)
}).ToList();
}
public List<TProvider> GetAvailableProviders()
{
return All().Where(c => c.Enable && c.Settings.Validate().IsValid)
.Select(GetInstance).ToList();
}
public TProviderDefinition Get(int id)
{
return _providerRepository.Get(id);
}
public TProviderDefinition Create(TProviderDefinition provider)
{
return _providerRepository.Insert(provider);
}
public void Update(TProviderDefinition definition)
{
_providerRepository.Update(definition);
}
public void Delete(int id)
{
_providerRepository.Delete(id);
}
private TProvider GetInstance(TProviderDefinition definition)
{
var type = GetImplementation(definition);
var instance = (TProvider)Activator.CreateInstance(type);
instance.Definition = definition;
return instance;
}
private Type GetImplementation(TProviderDefinition definition)
{
return _providers.Select(c => c.GetType()).SingleOrDefault(c => c.Name.Equals(definition.Implementation, StringComparison.InvariantCultureIgnoreCase));
}
public void Handle(ApplicationStartedEvent message)
{
_logger.Debug("Initializing Providers. Count {0}", _providers.Count);
RemoveMissingImplementations();
InitializeProviders();
}
protected virtual void InitializeProviders()
{
}
private void RemoveMissingImplementations()
{
var storedProvider = _providerRepository.All();
foreach (var invalidDefinition in storedProvider.Where(def => GetImplementation(def) == null))
{
_logger.Debug("Removing {0} ", invalidDefinition.Name);
_providerRepository.Delete(invalidDefinition);
}
}
}
}

View File

@ -0,0 +1,16 @@
using System;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.ThingiProvider
{
public class ProviderRepository<TProviderDefinition> : BasicRepository<TProviderDefinition>, IProviderRepository<TProviderDefinition>
where TProviderDefinition : ModelBase,
new()
{
protected ProviderRepository(IDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
}
}

View File

@ -1,5 +1,7 @@
using FluentAssertions;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Integration.Test
{
@ -13,6 +15,7 @@ public void should_have_built_in_indexer()
indexers.Should().NotBeEmpty();
indexers.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name));
indexers.Where(c => c.ConfigContract == typeof(NullConfig).Name).Should().OnlyContain(c => c.Enable);
}
}
}

View File

@ -1,4 +1,5 @@
using FluentAssertions;
using System.Threading;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Api.Indexers;
@ -10,10 +11,13 @@ public class ReleaseIntegrationTest : IntegrationTest
[Test]
public void should_only_have_unknown_series_releases()
{
var releases = Releases.All();
var indexers = Indexers.All();
releases.Should().OnlyContain(c => c.Rejections.Contains("Unknown Series"));
releases.Should().OnlyContain(c=>BeValidRelease(c));
releases.Should().OnlyContain(c => BeValidRelease(c));
}

View File

@ -7,7 +7,7 @@
<FrameworkUtilisationTypeForGallio>Disabled</FrameworkUtilisationTypeForGallio>
<FrameworkUtilisationTypeForMSpec>Disabled</FrameworkUtilisationTypeForMSpec>
<FrameworkUtilisationTypeForMSTest>Disabled</FrameworkUtilisationTypeForMSTest>
<EngineModes>Run all tests automatically:BFRydWU=;Run all tests manually:BUZhbHNl;Run impacted tests automatically, others manually (experimental!):CklzSW1wYWN0ZWQ=;Run pinned tests automatically, others manually:CElzUGlubmVk;Fast:DlN0cnVjdHVyYWxOb2RlBAAAABNEb2VzTm90SGF2ZUNhdGVnb3J5D0ludGVncmF0aW9uVGVzdBNEb2VzTm90SGF2ZUNhdGVnb3J5BkRiVGVzdApJc0ltcGFjdGVkE0RvZXNOb3RIYXZlQ2F0ZWdvcnkORGlza0FjY2Vzc1Rlc3QAAAAAAAAAAAAAAAA=</EngineModes>
<EngineModes>Run all tests automatically:BFRydWU=;Run all tests manually:BUZhbHNl;Run impacted tests automatically, others manually (experimental!):CklzSW1wYWN0ZWQ=;Run pinned tests automatically, others manually:CElzUGlubmVk;Fast:DlN0cnVjdHVyYWxOb2RlBQAAABNEb2VzTm90SGF2ZUNhdGVnb3J5D0ludGVncmF0aW9uVGVzdBNEb2VzTm90SGF2ZUNhdGVnb3J5BkRiVGVzdApJc0ltcGFjdGVkE0RvZXNOb3RIYXZlQ2F0ZWdvcnkORGlza0FjY2Vzc1Rlc3QISXNQaW5uZWQAAAAAAAAAAAAAAAABAAAA</EngineModes>
<MetricsExclusionList>
</MetricsExclusionList>
</SolutionConfiguration>