diff --git a/Marr.Data/Converters/BooleanIntConverter.cs b/Marr.Data/Converters/BooleanIntConverter.cs index 9dc9b41d3..18c964d15 100644 --- a/Marr.Data/Converters/BooleanIntConverter.cs +++ b/Marr.Data/Converters/BooleanIntConverter.cs @@ -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 @@ namespace Marr.Data.Converters 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) diff --git a/Marr.Data/Converters/BooleanYNConverter.cs b/Marr.Data/Converters/BooleanYNConverter.cs index b116bdeaf..38003939c 100644 --- a/Marr.Data/Converters/BooleanYNConverter.cs +++ b/Marr.Data/Converters/BooleanYNConverter.cs @@ -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 @@ namespace Marr.Data.Converters 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) diff --git a/Marr.Data/Converters/CastConverter.cs b/Marr.Data/Converters/CastConverter.cs index 4253357ed..2fa3b8eca 100644 --- a/Marr.Data/Converters/CastConverter.cs +++ b/Marr.Data/Converters/CastConverter.cs @@ -30,10 +30,15 @@ namespace Marr.Data.Converters 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) diff --git a/Marr.Data/Converters/ConverterContext.cs b/Marr.Data/Converters/ConverterContext.cs new file mode 100644 index 000000000..341925077 --- /dev/null +++ b/Marr.Data/Converters/ConverterContext.cs @@ -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; } + } +} \ No newline at end of file diff --git a/Marr.Data/Converters/EnumIntConverter.cs b/Marr.Data/Converters/EnumIntConverter.cs index a7d528d16..5fe88a411 100644 --- a/Marr.Data/Converters/EnumIntConverter.cs +++ b/Marr.Data/Converters/EnumIntConverter.cs @@ -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) diff --git a/Marr.Data/Converters/EnumStringConverter.cs b/Marr.Data/Converters/EnumStringConverter.cs index 4e304fc10..eb4f8b01a 100644 --- a/Marr.Data/Converters/EnumStringConverter.cs +++ b/Marr.Data/Converters/EnumStringConverter.cs @@ -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) diff --git a/Marr.Data/Converters/IConverter.cs b/Marr.Data/Converters/IConverter.cs index 318f26957..f2e9685a9 100644 --- a/Marr.Data/Converters/IConverter.cs +++ b/Marr.Data/Converters/IConverter.cs @@ -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; } diff --git a/Marr.Data/Mapping/MappingHelper.cs b/Marr.Data/Mapping/MappingHelper.cs index f025528be..0b4c73681 100644 --- a/Marr.Data/Mapping/MappingHelper.cs +++ b/Marr.Data/Mapping/MappingHelper.cs @@ -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 @@ namespace Marr.Data.Mapping // 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) diff --git a/Marr.Data/Marr.Data.csproj b/Marr.Data/Marr.Data.csproj index f7e58cb01..9ae7c2082 100644 --- a/Marr.Data/Marr.Data.csproj +++ b/Marr.Data/Marr.Data.csproj @@ -52,6 +52,7 @@ + diff --git a/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs b/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs index bf3d72d11..385a9b989 100644 --- a/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs +++ b/NzbDrone.Api.Test/ClientSchemaTests/SchemaBuilderFixture.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Api.Test.ClientSchemaTests [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 @@ namespace NzbDrone.Api.Test.ClientSchemaTests 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"); diff --git a/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs b/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs index 82fb9425a..e03e02c35 100644 --- a/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs +++ b/NzbDrone.Api.Test/MappingTests/ResourceMappingFixture.cs @@ -36,7 +36,7 @@ namespace NzbDrone.Api.Test.MappingTests [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))] diff --git a/NzbDrone.Api/ClientSchema/SchemaBuilder.cs b/NzbDrone.Api/ClientSchema/SchemaBuilder.cs index 99b2c6d15..c5c96c302 100644 --- a/NzbDrone.Api/ClientSchema/SchemaBuilder.cs +++ b/NzbDrone.Api/ClientSchema/SchemaBuilder.cs @@ -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 GenerateSchema(object model) + public static List ToSchema(object model) { + Ensure.That(() => model).IsNotNull(); + var properties = model.GetType().GetSimpleProperties(); var result = new List(properties.Count); @@ -50,10 +54,55 @@ namespace NzbDrone.Api.ClientSchema } + + public static object ReadFormSchema(List 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(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)) + { + var intValue = field.Value.ToString().ParseInt32(); + propertyInfo.SetValue(target, intValue, null); + } + + else + { + propertyInfo.SetValue(target, field.Value, null); + } + } + } + + return target; + + } + + public static T ReadFormSchema(List fields) + { + return (T)ReadFormSchema(fields, typeof (T)); + } + private static List 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(); } diff --git a/NzbDrone.Api/ClientSchema/SchemaDeserializer.cs b/NzbDrone.Api/ClientSchema/SchemaDeserializer.cs index 9985ffb49..36d2a68e8 100644 --- a/NzbDrone.Api/ClientSchema/SchemaDeserializer.cs +++ b/NzbDrone.Api/ClientSchema/SchemaDeserializer.cs @@ -8,38 +8,6 @@ namespace NzbDrone.Api.ClientSchema { public static class SchemaDeserializer { - public static T DeserializeSchema(T model, List fields) - { - var properties = model.GetType().GetSimpleProperties(); - - foreach (var propertyInfo in properties) - { - var fieldAttribute = propertyInfo.GetAttribute(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)) - { - var intValue = field.Value.ToString().ParseInt32(); - propertyInfo.SetValue(model, intValue, null); - } - - else - { - propertyInfo.SetValue(model, field.Value, null); - } - } - } - - return model; - } + } } \ No newline at end of file diff --git a/NzbDrone.Api/IndexerResource.cs b/NzbDrone.Api/IndexerResource.cs new file mode 100644 index 000000000..65c5bad64 --- /dev/null +++ b/NzbDrone.Api/IndexerResource.cs @@ -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 Fields { get; set; } + public String Implementation { get; set; } + public String ConfigContract { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Api/Indexers/IndexerModule.cs b/NzbDrone.Api/Indexers/IndexerModule.cs index 02215d3ed..33c56a108 100644 --- a/NzbDrone.Api/Indexers/IndexerModule.cs +++ b/NzbDrone.Api/Indexers/IndexerModule.cs @@ -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 + public class IndexerModule : ProviderModuleBase { - 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(); - } - - private List GetAll() - { - var indexers = _indexerService.All(); - - var result = new List(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); } } } \ No newline at end of file diff --git a/NzbDrone.Api/Indexers/IndexerResource.cs b/NzbDrone.Api/Indexers/IndexerResource.cs index 34a31551a..a613526fe 100644 --- a/NzbDrone.Api/Indexers/IndexerResource.cs +++ b/NzbDrone.Api/Indexers/IndexerResource.cs @@ -11,5 +11,6 @@ namespace NzbDrone.Api.Indexers public String Name { get; set; } public List Fields { get; set; } public String Implementation { get; set; } + public String ConfigContract { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Api/Indexers/IndexerSchemaModule.cs b/NzbDrone.Api/Indexers/IndexerSchemaModule.cs index ad2ca7846..a0312bdc4 100644 --- a/NzbDrone.Api/Indexers/IndexerSchemaModule.cs +++ b/NzbDrone.Api/Indexers/IndexerSchemaModule.cs @@ -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 { - 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 GetSchema() { - var indexers = _indexerService.Schema(); - var result = new List(indexers.Count); + var indexers = _indexerFactory.Templates().Where(c => c.Implementation =="Newznab"); + + + var result = new List(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); } diff --git a/NzbDrone.Api/Notifications/NotificationModule.cs b/NzbDrone.Api/Notifications/NotificationModule.cs index bc7ca8aac..c8be13f67 100644 --- a/NzbDrone.Api/Notifications/NotificationModule.cs +++ b/NzbDrone.Api/Notifications/NotificationModule.cs @@ -4,6 +4,7 @@ using System.Linq; 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 @@ namespace NzbDrone.Api.Notifications { 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 @@ namespace NzbDrone.Api.Notifications } 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; } diff --git a/NzbDrone.Api/Notifications/NotificationSchemaModule.cs b/NzbDrone.Api/Notifications/NotificationSchemaModule.cs index d347e343c..68a5bd594 100644 --- a/NzbDrone.Api/Notifications/NotificationSchemaModule.cs +++ b/NzbDrone.Api/Notifications/NotificationSchemaModule.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Api.Notifications { 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); diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index 8de55e952..88f133b26 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -109,6 +109,8 @@ + + diff --git a/NzbDrone.Api/ProviderModuleBase.cs b/NzbDrone.Api/ProviderModuleBase.cs new file mode 100644 index 000000000..f379f598a --- /dev/null +++ b/NzbDrone.Api/ProviderModuleBase.cs @@ -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 : NzbDroneRestModule + where TProviderDefinition : ProviderDefinition, new() + where TProvider : IProvider + where TProviderResource : ProviderResource, new() + { + private readonly IProviderFactory _providerFactory; + + protected ProviderModuleBase(IProviderFactory 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(); + } + + private List GetAll() + { + var indexerDefinitions = _providerFactory.All(); + + var result = new List(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(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(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.App.Test/ContainerFixture.cs b/NzbDrone.App.Test/ContainerFixture.cs index 14a35688d..a165f9348 100644 --- a/NzbDrone.App.Test/ContainerFixture.cs +++ b/NzbDrone.App.Test/ContainerFixture.cs @@ -37,7 +37,7 @@ namespace NzbDrone.App.Test { var factory = MainAppContainerBuilder.BuildContainer(args).Resolve(); - factory.Build().Should().NotBeNull(); + factory.Build().Should().NotBeNull(); } [Test] diff --git a/NzbDrone.Common/Reflection/ReflectionExtensions.cs b/NzbDrone.Common/Reflection/ReflectionExtensions.cs index 98ff9ff12..898d45119 100644 --- a/NzbDrone.Common/Reflection/ReflectionExtensions.cs +++ b/NzbDrone.Common/Reflection/ReflectionExtensions.cs @@ -7,6 +7,8 @@ namespace NzbDrone.Common.Reflection { public static class ReflectionExtensions { + public static readonly Assembly CoreAssembly = Assembly.Load("NzbDrone.Core"); + public static List GetSimpleProperties(this Type type) { var properties = type.GetProperties(); @@ -58,6 +60,11 @@ namespace NzbDrone.Common.Reflection 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(this Type type) { return type.GetCustomAttributes(typeof(TAttribute), true).Any(); diff --git a/NzbDrone.Core.Test/Datastore/Converters/ProviderSettingConverterFixture.cs b/NzbDrone.Core.Test/Datastore/Converters/ProviderSettingConverterFixture.cs new file mode 100644 index 000000000..9aafbee73 --- /dev/null +++ b/NzbDrone.Core.Test/Datastore/Converters/ProviderSettingConverterFixture.cs @@ -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 + { + [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); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs b/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs index 2de52fb5f..bbfb8404a 100644 --- a/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs +++ b/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs @@ -13,7 +13,7 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.IndexerTests { - public class IndexerServiceFixture : DbTest + public class IndexerServiceFixture : DbTest { private List _indexers; @@ -57,10 +57,8 @@ namespace NzbDrone.Core.Test.IndexerTests 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 @@ namespace NzbDrone.Core.Test.IndexerTests var existingIndexers = Builder.CreateNew().BuildNew(); + existingIndexers.ConfigContract = typeof (NewznabSettings).Name; repo.Insert(existingIndexers); diff --git a/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs b/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs index 1be142824..28dc053e4 100644 --- a/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs +++ b/NzbDrone.Core.Test/IndexerTests/IntegrationTests/IndexerIntegrationTests.cs @@ -7,6 +7,7 @@ using NzbDrone.Core.Indexers.Wombles; 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 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests { 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 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests 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 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests 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); diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 58b09e81e..8a79c19af 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -102,6 +102,7 @@ + @@ -181,6 +182,8 @@ + + diff --git a/NzbDrone.Core.Test/ThingiProvider/ProviderBaseFixture.cs b/NzbDrone.Core.Test/ThingiProvider/ProviderBaseFixture.cs new file mode 100644 index 000000000..641e62dca --- /dev/null +++ b/NzbDrone.Core.Test/ThingiProvider/ProviderBaseFixture.cs @@ -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 + { + [Test] + public void should_read_write_download_provider() + { + var model = Builder.CreateNew().BuildNew(); + var newznabSettings = Builder.CreateNew().Build(); + model.Settings = newznabSettings; + Subject.Insert(model); + + var storedProvider = Subject.Single(); + + storedProvider.Settings.Should().BeOfType(); + + var storedSetting = (NewznabSettings)storedProvider.Settings; + + storedSetting.ShouldHave().AllProperties().EqualTo(newznabSettings); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ThingiProviderTests/NullConfigFixture.cs b/NzbDrone.Core.Test/ThingiProviderTests/NullConfigFixture.cs new file mode 100644 index 000000000..0f47d9a91 --- /dev/null +++ b/NzbDrone.Core.Test/ThingiProviderTests/NullConfigFixture.cs @@ -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 + { + [Test] + public void should_be_valid() + { + Subject.Validate().IsValid.Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/Converters/BooleanIntConverter.cs b/NzbDrone.Core/Datastore/Converters/BooleanIntConverter.cs index 88d06c669..7ec60e201 100644 --- a/NzbDrone.Core/Datastore/Converters/BooleanIntConverter.cs +++ b/NzbDrone.Core/Datastore/Converters/BooleanIntConverter.cs @@ -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 @@ namespace NzbDrone.Core.Datastore.Converters 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)clrValue; diff --git a/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs b/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs index b222eb320..e144d4cf4 100644 --- a/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs +++ b/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs @@ -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) diff --git a/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs b/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs index f79c736c2..40c2727bc 100644 --- a/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs +++ b/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs @@ -14,16 +14,21 @@ namespace NzbDrone.Core.Datastore.Converters } } - 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) diff --git a/NzbDrone.Core/Datastore/Converters/Int32Converter.cs b/NzbDrone.Core/Datastore/Converters/Int32Converter.cs index fcf66a045..c69bfb9a5 100644 --- a/NzbDrone.Core/Datastore/Converters/Int32Converter.cs +++ b/NzbDrone.Core/Datastore/Converters/Int32Converter.cs @@ -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) diff --git a/NzbDrone.Core/Datastore/Converters/ProviderSettingConverter.cs b/NzbDrone.Core/Datastore/Converters/ProviderSettingConverter.cs new file mode 100644 index 000000000..ace64d6af --- /dev/null +++ b/NzbDrone.Core/Datastore/Converters/ProviderSettingConverter.cs @@ -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); + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs b/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs index 839ef85be..6dc9d6c24 100644 --- a/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs +++ b/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs @@ -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; diff --git a/NzbDrone.Core/Datastore/Converters/UtcConverter.cs b/NzbDrone.Core/Datastore/Converters/UtcConverter.cs index 0bdc802dc..25d313a6c 100644 --- a/NzbDrone.Core/Datastore/Converters/UtcConverter.cs +++ b/NzbDrone.Core/Datastore/Converters/UtcConverter.cs @@ -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) diff --git a/NzbDrone.Core/Datastore/Migration/022_move_notification_to_generic_provider.cs b/NzbDrone.Core/Datastore/Migration/022_move_notification_to_generic_provider.cs new file mode 100644 index 000000000..2b5182419 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/022_move_notification_to_generic_provider.cs @@ -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(); + + 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(); + } + } + } + } + } + } +} diff --git a/NzbDrone.Core/Datastore/Migration/023_add_config_contract_to_indexers.cs b/NzbDrone.Core/Datastore/Migration/023_add_config_contract_to_indexers.cs new file mode 100644 index 000000000..cf2153605 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/023_add_config_contract_to_indexers.cs @@ -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"); + } + } +} diff --git a/NzbDrone.Core/Datastore/TableMapping.cs b/NzbDrone.Core/Datastore/TableMapping.cs index 533b3ab70..bade3c2d6 100644 --- a/NzbDrone.Core/Datastore/TableMapping.cs +++ b/NzbDrone.Core/Datastore/TableMapping.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; 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.Organizer; 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 @@ namespace NzbDrone.Core.Datastore private static void RegisterMappers() { RegisterEmbeddedConverter(); + RegisterProviderSettingConverter(); MapRepository.Instance.RegisterTypeConverter(typeof(Int32), new Int32Converter()); MapRepository.Instance.RegisterTypeConverter(typeof(DateTime), new UtcConverter()); @@ -78,10 +81,20 @@ namespace NzbDrone.Core.Datastore MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary), new EmbeddedDocumentConverter()); } + private static void RegisterProviderSettingConverter() + { + var settingTypes = typeof(IProviderConfig).Assembly.ImplementationsOf(); + + 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(); var embeddedConvertor = new EmbeddedDocumentConverter(); diff --git a/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index 5770687b8..96b52e8cf 100644 --- a/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -24,7 +24,7 @@ namespace NzbDrone.Core.IndexerSearch 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 @@ namespace NzbDrone.Core.IndexerSearch 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 @@ namespace NzbDrone.Core.IndexerSearch IMakeDownloadDecision makeDownloadDecision, Logger logger) { - _indexerService = indexerService; + _indexerFactory = indexerFactory; _feedFetcher = feedFetcher; _sceneMapping = sceneMapping; _seriesService = seriesService; @@ -132,7 +132,7 @@ namespace NzbDrone.Core.IndexerSearch private List Dispatch(Func> searchAction, SearchCriteriaBase criteriaBase) { - var indexers = _indexerService.GetAvailableIndexers().ToList(); + var indexers = _indexerFactory.GetAvailableProviders().ToList(); var reports = new List(); _logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase); diff --git a/NzbDrone.Core/Indexers/Eztv/Eztv.cs b/NzbDrone.Core/Indexers/Eztv/Eztv.cs index 541f3a078..52c55df60 100644 --- a/NzbDrone.Core/Indexers/Eztv/Eztv.cs +++ b/NzbDrone.Core/Indexers/Eztv/Eztv.cs @@ -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 { - 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 @@ namespace NzbDrone.Core.Indexers.Eztv { get { - return new[] - { - "http://www.ezrss.it/feed/" - }; + yield return "http://www.ezrss.it/feed/"; } } @@ -55,7 +43,7 @@ namespace NzbDrone.Core.Indexers.Eztv public override IEnumerable 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]; } } diff --git a/NzbDrone.Core/Indexers/FetchAndParseRssService.cs b/NzbDrone.Core/Indexers/FetchAndParseRssService.cs index c9633c8d0..534668b6b 100644 --- a/NzbDrone.Core/Indexers/FetchAndParseRssService.cs +++ b/NzbDrone.Core/Indexers/FetchAndParseRssService.cs @@ -14,13 +14,13 @@ namespace NzbDrone.Core.Indexers 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 @@ namespace NzbDrone.Core.Indexers { var result = new List(); - var indexers = _indexerService.GetAvailableIndexers().ToList(); + var indexers = _indexerFactory.GetAvailableProviders().ToList(); if (!indexers.Any()) { diff --git a/NzbDrone.Core/Indexers/IIndexer.cs b/NzbDrone.Core/Indexers/IIndexer.cs index a681ae3df..34daa6a26 100644 --- a/NzbDrone.Core/Indexers/IIndexer.cs +++ b/NzbDrone.Core/Indexers/IIndexer.cs @@ -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 DefaultDefinitions { get; } - - IndexerDefinition InstanceDefinition { get; set; } + IParseFeed Parser { get; } + DownloadProtocol Protocol { get; } IEnumerable RecentFeed { get; } - - IParseFeed Parser { get; } - IndexerKind Kind { get; } - IEnumerable GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber); IEnumerable GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date); IEnumerable GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset); diff --git a/NzbDrone.Core/Indexers/IndexerBase.cs b/NzbDrone.Core/Indexers/IndexerBase.cs index 6f544a2be..41942d35b 100644 --- a/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/NzbDrone.Core/Indexers/IndexerBase.cs @@ -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 : 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 DefaultDefinitions + public Type ConfigContract { get { + return typeof(TSettings); + } + } + + public virtual IEnumerable 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 RecentFeed { get; } public abstract IEnumerable GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber); public abstract IEnumerable GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date); public abstract IEnumerable GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset); + + + public override string ToString() + { + return GetType().Name; + } } - public enum IndexerKind + public enum DownloadProtocol { Usenet, Torrent diff --git a/NzbDrone.Core/Indexers/IndexerDefinition.cs b/NzbDrone.Core/Indexers/IndexerDefinition.cs index a061052e7..5909532b4 100644 --- a/NzbDrone.Core/Indexers/IndexerDefinition.cs +++ b/NzbDrone.Core/Indexers/IndexerDefinition.cs @@ -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; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/IndexerFactory.cs b/NzbDrone.Core/Indexers/IndexerFactory.cs new file mode 100644 index 000000000..b89f0d539 --- /dev/null +++ b/NzbDrone.Core/Indexers/IndexerFactory.cs @@ -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 + { + + } + + public class IndexerFactory : ProviderFactory, IIndexerFactory + { + private readonly IIndexerRepository _providerRepository; + private readonly IEnumerable _providers; + + public IndexerFactory(IIndexerRepository providerRepository, IEnumerable 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().ToList()); + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/IndexerFetchService.cs b/NzbDrone.Core/Indexers/IndexerFetchService.cs index ba284c8b7..b571f6466 100644 --- a/NzbDrone.Core/Indexers/IndexerFetchService.cs +++ b/NzbDrone.Core/Indexers/IndexerFetchService.cs @@ -33,11 +33,11 @@ namespace NzbDrone.Core.Indexers public virtual IList 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 @@ namespace NzbDrone.Core.Indexers 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 @@ namespace NzbDrone.Core.Indexers 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 @@ namespace NzbDrone.Core.Indexers 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 @@ namespace NzbDrone.Core.Indexers 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 @@ namespace NzbDrone.Core.Indexers 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 @@ namespace NzbDrone.Core.Indexers } } - result.ForEach(c => c.Indexer = indexer.Name); + result.ForEach(c => c.Indexer = indexer.Definition.Name); return result; } diff --git a/NzbDrone.Core/Indexers/IndexerRepository.cs b/NzbDrone.Core/Indexers/IndexerRepository.cs index b4c6446a1..269bb5507 100644 --- a/NzbDrone.Core/Indexers/IndexerRepository.cs +++ b/NzbDrone.Core/Indexers/IndexerRepository.cs @@ -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 + public interface IIndexerRepository : IProviderRepository { - IndexerDefinition Get(string name); - IndexerDefinition Find(string name); + } - public class IndexerRepository : BasicRepository, IIndexerRepository + public class IndexerRepository : ProviderRepository, 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)); - } - } } diff --git a/NzbDrone.Core/Indexers/IndexerService.cs b/NzbDrone.Core/Indexers/IndexerService.cs deleted file mode 100644 index e1b49c2dd..000000000 --- a/NzbDrone.Core/Indexers/IndexerService.cs +++ /dev/null @@ -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 All(); - List GetAvailableIndexers(); - Indexer Get(int id); - Indexer Get(string name); - List Schema(); - Indexer Create(Indexer indexer); - Indexer Update(Indexer indexer); - void Delete(int id); - } - - public class IndexerService : IIndexerService, IHandle - { - private readonly IIndexerRepository _indexerRepository; - private readonly IConfigFileProvider _configFileProvider; - private readonly INewznabTestService _newznabTestService; - private readonly Logger _logger; - - private readonly List _indexers; - - public IndexerService(IIndexerRepository indexerRepository, - IEnumerable 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 All() - { - return _indexerRepository.All().Select(ToIndexer).ToList(); - } - - public List 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 Schema() - { - var indexers = new List(); - - 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); - } - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/IndexerSettingProvider.cs b/NzbDrone.Core/Indexers/IndexerSettingProvider.cs deleted file mode 100644 index ab7da9e58..000000000 --- a/NzbDrone.Core/Indexers/IndexerSettingProvider.cs +++ /dev/null @@ -1,31 +0,0 @@ -using NzbDrone.Common.Serializer; - -namespace NzbDrone.Core.Indexers -{ - public interface IProviderIndexerSetting - { - TSetting Get(IIndexer indexer) where TSetting : IIndexerSetting, new(); - } - - public class IndexerSettingProvider : IProviderIndexerSetting - { - private readonly IIndexerRepository _indexerRepository; - - public IndexerSettingProvider(IIndexerRepository indexerRepository) - { - _indexerRepository = indexerRepository; - } - - public TSetting Get(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(indexerDef.Settings); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/IndexerSettingUpdatedEvent.cs b/NzbDrone.Core/Indexers/IndexerSettingUpdatedEvent.cs index 8b3edc513..783948b2e 100644 --- a/NzbDrone.Core/Indexers/IndexerSettingUpdatedEvent.cs +++ b/NzbDrone.Core/Indexers/IndexerSettingUpdatedEvent.cs @@ -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; diff --git a/NzbDrone.Core/Indexers/IndexerWithSetting.cs b/NzbDrone.Core/Indexers/IndexerWithSetting.cs deleted file mode 100644 index 78db54932..000000000 --- a/NzbDrone.Core/Indexers/IndexerWithSetting.cs +++ /dev/null @@ -1,21 +0,0 @@ -using NzbDrone.Common.Serializer; - -namespace NzbDrone.Core.Indexers -{ - public abstract class IndexerWithSetting : 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(json) ?? new TSetting(); - - return Settings; - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/NzbDrone.Core/Indexers/Newznab/Newznab.cs index 8a13d2282..a4c106846 100644 --- a/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -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 + public class Newznab : IndexerBase { public override IParseFeed Parser { @@ -15,7 +16,7 @@ namespace NzbDrone.Core.Indexers.Newznab } } - public override IEnumerable DefaultDefinitions + public override IEnumerable DefaultDefinitions { get { @@ -51,7 +52,7 @@ namespace NzbDrone.Core.Indexers.Newznab } } - private string GetSettings(string url, List categories) + private NewznabSettings GetSettings(string url, List categories) { var settings = new NewznabSettings { Url = url }; @@ -60,7 +61,7 @@ namespace NzbDrone.Core.Indexers.Newznab settings.Categories = categories; } - return settings.ToJson(); + return settings; } public override IEnumerable RecentFeed @@ -68,7 +69,7 @@ namespace NzbDrone.Core.Indexers.Newznab 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 { 5000 }; } @@ -114,19 +115,11 @@ namespace NzbDrone.Core.Indexers.Newznab 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 @@ namespace NzbDrone.Core.Indexers.Newznab return title.Replace("+", "%20"); } } -} \ No newline at end of file +} diff --git a/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs b/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs index c83623ace..5f05ba96d 100644 --- a/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs +++ b/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; 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 @@ namespace NzbDrone.Core.Indexers.Newznab } - public class NewznabSettings : IIndexerSetting + public class NewznabSettings : IProviderConfig { private static readonly NewznabSettingsValidator Validator = new NewznabSettingsValidator(); diff --git a/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs b/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs index 15f6dbbe5..c4daeab66 100644 --- a/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs +++ b/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs @@ -3,18 +3,13 @@ using System.Collections.Generic; namespace NzbDrone.Core.Indexers.Omgwtfnzbs { - public class Omgwtfnzbs : IndexerWithSetting + public class Omgwtfnzbs : IndexerBase { - public override string Name - { - get { return "omgwtfnzbs"; } - } - - public override IndexerKind Kind + public override DownloadProtocol Protocol { get { - return IndexerKind.Usenet; + return DownloadProtocol.Usenet; } } diff --git a/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs b/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs index 778b3a48e..900055608 100644 --- a/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs +++ b/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs @@ -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 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs } } - public class OmgwtfnzbsSettings : IIndexerSetting + public class OmgwtfnzbsSettings : IProviderConfig { private static readonly OmgwtfnzbsSettingsValidator Validator = new OmgwtfnzbsSettingsValidator(); diff --git a/NzbDrone.Core/Indexers/Wombles/Wombles.cs b/NzbDrone.Core/Indexers/Wombles/Wombles.cs index 95cd559de..5355d853d 100644 --- a/NzbDrone.Core/Indexers/Wombles/Wombles.cs +++ b/NzbDrone.Core/Indexers/Wombles/Wombles.cs @@ -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 { - public override string Name - { - get { return "WomblesIndex"; } - } - - public override IndexerKind Kind + public override DownloadProtocol Protocol { get { - return IndexerKind.Usenet; + return DownloadProtocol.Usenet; } } diff --git a/NzbDrone.Core/Notifications/Email/EmailSettings.cs b/NzbDrone.Core/Notifications/Email/EmailSettings.cs index 6ee61b6a2..42aa725cd 100644 --- a/NzbDrone.Core/Notifications/Email/EmailSettings.cs +++ b/NzbDrone.Core/Notifications/Email/EmailSettings.cs @@ -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 @@ namespace NzbDrone.Core.Notifications.Email return !string.IsNullOrWhiteSpace(Server) && Port > 0 && !string.IsNullOrWhiteSpace(From) && !string.IsNullOrWhiteSpace(To); } } + + public ValidationResult Validate() + { + throw new NotImplementedException(); + } } } diff --git a/NzbDrone.Core/Notifications/Growl/GrowlSettings.cs b/NzbDrone.Core/Notifications/Growl/GrowlSettings.cs index c66060a94..dd049268d 100644 --- a/NzbDrone.Core/Notifications/Growl/GrowlSettings.cs +++ b/NzbDrone.Core/Notifications/Growl/GrowlSettings.cs @@ -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 @@ namespace NzbDrone.Core.Notifications.Growl return !string.IsNullOrWhiteSpace(Host) && !string.IsNullOrWhiteSpace(Password) && Port > 0; } } + + public ValidationResult Validate() + { + throw new NotImplementedException(); + } } } diff --git a/NzbDrone.Core/Notifications/INotifcationSettings.cs b/NzbDrone.Core/Notifications/INotifcationSettings.cs deleted file mode 100644 index 49e113de4..000000000 --- a/NzbDrone.Core/Notifications/INotifcationSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NzbDrone.Core.Notifications -{ - public interface INotifcationSettings - { - bool IsValid { get; } - } -} diff --git a/NzbDrone.Core/Notifications/Notification.cs b/NzbDrone.Core/Notifications/Notification.cs index 09919c958..40275ecf9 100644 --- a/NzbDrone.Core/Notifications/Notification.cs +++ b/NzbDrone.Core/Notifications/Notification.cs @@ -1,4 +1,6 @@ -namespace NzbDrone.Core.Notifications +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.Notifications { public class Notification { @@ -8,7 +10,7 @@ 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; } } diff --git a/NzbDrone.Core/Notifications/NotificationBase.cs b/NzbDrone.Core/Notifications/NotificationBase.cs index e8577c978..d121bd44b 100644 --- a/NzbDrone.Core/Notifications/NotificationBase.cs +++ b/NzbDrone.Core/Notifications/NotificationBase.cs @@ -1,9 +1,10 @@ using NzbDrone.Common.Serializer; +using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications { - public abstract class NotificationBase : INotification where TSetting : class, INotifcationSettings, new() + public abstract class NotificationBase : INotification where TSetting : class, IProviderConfig, new() { public abstract string Name { get; } public abstract string ImplementationName { get; } diff --git a/NzbDrone.Core/Notifications/NotificationDefinition.cs b/NzbDrone.Core/Notifications/NotificationDefinition.cs index 7456c7783..41fc64509 100644 --- a/NzbDrone.Core/Notifications/NotificationDefinition.cs +++ b/NzbDrone.Core/Notifications/NotificationDefinition.cs @@ -1,5 +1,6 @@ using System; using NzbDrone.Core.Datastore; +using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications { @@ -11,4 +12,11 @@ namespace NzbDrone.Core.Notifications public String Settings { get; set; } public String Implementation { get; set; } } + + + public class NotificationProviderModel : ProviderDefinition + { + public Boolean OnGrab { get; set; } + public Boolean OnDownload { get; set; } + } } \ No newline at end of file diff --git a/NzbDrone.Core/Notifications/NotificationService.cs b/NzbDrone.Core/Notifications/NotificationService.cs index 0553f4669..458c80d65 100644 --- a/NzbDrone.Core/Notifications/NotificationService.cs +++ b/NzbDrone.Core/Notifications/NotificationService.cs @@ -7,6 +7,7 @@ using NzbDrone.Common.Serializer; 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 @@ namespace NzbDrone.Core.Notifications Notification Get(int id); List Schema(); Notification Create(Notification notification); - Notification Update(Notification notification); + void Update(Notification notification); void Delete(int id); } @@ -71,7 +72,7 @@ namespace NzbDrone.Core.Notifications 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 @@ namespace NzbDrone.Core.Notifications 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) diff --git a/NzbDrone.Core/Notifications/NotificationSettingsProvider.cs b/NzbDrone.Core/Notifications/NotificationSettingsProvider.cs index 53f4d8a58..b26afa1fd 100644 --- a/NzbDrone.Core/Notifications/NotificationSettingsProvider.cs +++ b/NzbDrone.Core/Notifications/NotificationSettingsProvider.cs @@ -1,10 +1,11 @@ using NzbDrone.Common.Serializer; +using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Notifications { public interface INotificationSettingsProvider { - TSetting Get(INotification indexer) where TSetting : INotifcationSettings, new(); + TSetting Get(INotification indexer) where TSetting : IProviderConfig, new(); } public class NotificationSettingsProvider : INotificationSettingsProvider @@ -16,7 +17,7 @@ namespace NzbDrone.Core.Notifications _notificationRepository = notificationRepository; } - public TSetting Get(INotification indexer) where TSetting : INotifcationSettings, new() + public TSetting Get(INotification indexer) where TSetting : IProviderConfig, new() { var indexerDef = _notificationRepository.Find(indexer.Name); diff --git a/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs b/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs index c8c941239..48c8c6fc6 100644 --- a/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs +++ b/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs @@ -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 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid return !String.IsNullOrWhiteSpace(ApiKey) && Priority != null & Priority >= -1 && Priority <= 2; } } + + public ValidationResult Validate() + { + throw new NotImplementedException(); + } } } diff --git a/NzbDrone.Core/Notifications/Plex/PlexClientSettings.cs b/NzbDrone.Core/Notifications/Plex/PlexClientSettings.cs index 130552e52..a0d762406 100644 --- a/NzbDrone.Core/Notifications/Plex/PlexClientSettings.cs +++ b/NzbDrone.Core/Notifications/Plex/PlexClientSettings.cs @@ -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 @@ namespace NzbDrone.Core.Notifications.Plex return !string.IsNullOrWhiteSpace(Host); } } + + public ValidationResult Validate() + { + throw new NotImplementedException(); + } } } diff --git a/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs b/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs index 40b05a2c0..ed410767b 100644 --- a/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs +++ b/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs @@ -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 @@ namespace NzbDrone.Core.Notifications.Plex return !string.IsNullOrWhiteSpace(Host); } } + + public ValidationResult Validate() + { + throw new NotImplementedException(); + } } } diff --git a/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs b/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs index f6b9a7603..66b574179 100644 --- a/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs +++ b/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs @@ -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 @@ namespace NzbDrone.Core.Notifications.Prowl return !string.IsNullOrWhiteSpace(ApiKey) && Priority != null & Priority >= -2 && Priority <= 2; } } + + public ValidationResult Validate() + { + throw new NotImplementedException(); + } } } diff --git a/NzbDrone.Core/Notifications/PushBullet/PushBulletSettings.cs b/NzbDrone.Core/Notifications/PushBullet/PushBulletSettings.cs index f7a492622..1886991d8 100644 --- a/NzbDrone.Core/Notifications/PushBullet/PushBulletSettings.cs +++ b/NzbDrone.Core/Notifications/PushBullet/PushBulletSettings.cs @@ -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 @@ namespace NzbDrone.Core.Notifications.PushBullet return !String.IsNullOrWhiteSpace(ApiKey) && DeviceId > 0; } } + + public ValidationResult Validate() + { + throw new NotImplementedException(); + } } } diff --git a/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs b/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs index 43c7937b6..ca4c332c7 100644 --- a/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs +++ b/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs @@ -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 @@ namespace NzbDrone.Core.Notifications.Pushover return !string.IsNullOrWhiteSpace(UserKey) && Priority != null & Priority >= -1 && Priority <= 2; } } + + public ValidationResult Validate() + { + throw new NotImplementedException(); + } } } diff --git a/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs b/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs index 0a09d9d57..2c0569c89 100644 --- a/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs +++ b/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs @@ -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 @@ namespace NzbDrone.Core.Notifications.Xbmc return !string.IsNullOrWhiteSpace(Host) && Port > 0; } } + + public ValidationResult Validate() + { + throw new NotImplementedException(); + } } } diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 3c48dd31d..766536d42 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -141,6 +141,7 @@ + @@ -172,6 +173,8 @@ + + @@ -241,12 +244,10 @@ - - @@ -343,13 +344,11 @@ - - @@ -371,7 +370,6 @@ - @@ -417,6 +415,8 @@ + + @@ -427,6 +427,13 @@ + + + + + + + @@ -505,7 +512,7 @@ Code - + Code diff --git a/NzbDrone.Core/NzbDrone.Core.ncrunchproject b/NzbDrone.Core/NzbDrone.Core.ncrunchproject index b2eed192e..b36cf9e27 100644 --- a/NzbDrone.Core/NzbDrone.Core.ncrunchproject +++ b/NzbDrone.Core/NzbDrone.Core.ncrunchproject @@ -13,7 +13,7 @@ true true 5000 - Debug + RELEASE x86 x86 diff --git a/NzbDrone.Core/ThingiProvider/ConfigContractNotFoundException.cs b/NzbDrone.Core/ThingiProvider/ConfigContractNotFoundException.cs new file mode 100644 index 000000000..47d6ecdf3 --- /dev/null +++ b/NzbDrone.Core/ThingiProvider/ConfigContractNotFoundException.cs @@ -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) + { + } + + } +} diff --git a/NzbDrone.Core/ThingiProvider/IProvider.cs b/NzbDrone.Core/ThingiProvider/IProvider.cs new file mode 100644 index 000000000..4947c3d8b --- /dev/null +++ b/NzbDrone.Core/ThingiProvider/IProvider.cs @@ -0,0 +1,14 @@ + +using System; +using System.Collections.Generic; + +namespace NzbDrone.Core.ThingiProvider +{ + public interface IProvider + { + Type ConfigContract { get; } + + IEnumerable DefaultDefinitions { get; } + ProviderDefinition Definition { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/IIndexerSetting.cs b/NzbDrone.Core/ThingiProvider/IProviderConfig.cs similarity index 53% rename from NzbDrone.Core/Indexers/IIndexerSetting.cs rename to NzbDrone.Core/ThingiProvider/IProviderConfig.cs index 4f9cf16de..46501fb34 100644 --- a/NzbDrone.Core/Indexers/IIndexerSetting.cs +++ b/NzbDrone.Core/ThingiProvider/IProviderConfig.cs @@ -1,9 +1,9 @@ using FluentValidation.Results; -namespace NzbDrone.Core.Indexers +namespace NzbDrone.Core.ThingiProvider { - public interface IIndexerSetting + public interface IProviderConfig { ValidationResult Validate(); } -} +} \ No newline at end of file diff --git a/NzbDrone.Core/ThingiProvider/IProviderFactory.cs b/NzbDrone.Core/ThingiProvider/IProviderFactory.cs new file mode 100644 index 000000000..54e8315a6 --- /dev/null +++ b/NzbDrone.Core/ThingiProvider/IProviderFactory.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.ThingiProvider +{ + public interface IProviderFactory + where TProviderDefinition : ProviderDefinition, new() + where TProvider : IProvider + { + List All(); + List GetAvailableProviders(); + TProviderDefinition Get(int id); + TProviderDefinition Create(TProviderDefinition indexer); + void Update(TProviderDefinition indexer); + void Delete(int id); + List Templates(); + } +} \ No newline at end of file diff --git a/NzbDrone.Core/ThingiProvider/IProviderRepository.cs b/NzbDrone.Core/ThingiProvider/IProviderRepository.cs new file mode 100644 index 000000000..13043b64d --- /dev/null +++ b/NzbDrone.Core/ThingiProvider/IProviderRepository.cs @@ -0,0 +1,8 @@ +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.ThingiProvider +{ + public interface IProviderRepository : IBasicRepository where TProvider : ModelBase, new() + { + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/NullSetting.cs b/NzbDrone.Core/ThingiProvider/NullConfig.cs similarity index 50% rename from NzbDrone.Core/Indexers/NullSetting.cs rename to NzbDrone.Core/ThingiProvider/NullConfig.cs index a7013335a..6eea2e66c 100644 --- a/NzbDrone.Core/Indexers/NullSetting.cs +++ b/NzbDrone.Core/ThingiProvider/NullConfig.cs @@ -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() { diff --git a/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs b/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs new file mode 100644 index 000000000..ef21d3206 --- /dev/null +++ b/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs @@ -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; + } + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/ThingiProvider/ProviderFactory.cs b/NzbDrone.Core/ThingiProvider/ProviderFactory.cs new file mode 100644 index 000000000..bbd909bb3 --- /dev/null +++ b/NzbDrone.Core/ThingiProvider/ProviderFactory.cs @@ -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 : IProviderFactory, IHandle + where TProviderDefinition : ProviderDefinition, new() + where TProvider : IProvider + { + private readonly IProviderRepository _providerRepository; + private readonly Logger _logger; + + private readonly List _providers; + + protected ProviderFactory(IProviderRepository providerRepository, IEnumerable providers, Logger logger) + { + _providerRepository = providerRepository; + _providers = providers.ToList(); + _logger = logger; + } + + public List All() + { + return _providerRepository.All().ToList(); + } + + public List Templates() + { + return _providers.Select(p => new TProviderDefinition() + { + ConfigContract = p.ConfigContract.Name, + Implementation = p.GetType().Name, + Settings = (IProviderConfig)Activator.CreateInstance(p.ConfigContract) + }).ToList(); + } + + public List 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); + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/ThingiProvider/ProviderRepository.cs b/NzbDrone.Core/ThingiProvider/ProviderRepository.cs new file mode 100644 index 000000000..cceaaefcb --- /dev/null +++ b/NzbDrone.Core/ThingiProvider/ProviderRepository.cs @@ -0,0 +1,16 @@ +using System; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.ThingiProvider +{ + public class ProviderRepository : BasicRepository, IProviderRepository + where TProviderDefinition : ModelBase, + new() + { + protected ProviderRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + } +} \ No newline at end of file diff --git a/NzbDrone.Integration.Test/IndexerIntegrationFixture.cs b/NzbDrone.Integration.Test/IndexerIntegrationFixture.cs index 33fb6e0ab..96003762b 100644 --- a/NzbDrone.Integration.Test/IndexerIntegrationFixture.cs +++ b/NzbDrone.Integration.Test/IndexerIntegrationFixture.cs @@ -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 @@ namespace NzbDrone.Integration.Test indexers.Should().NotBeEmpty(); indexers.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name)); + indexers.Where(c => c.ConfigContract == typeof(NullConfig).Name).Should().OnlyContain(c => c.Enable); } } } \ No newline at end of file diff --git a/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs b/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs index b9a86674e..eb76f1255 100644 --- a/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs +++ b/NzbDrone.Integration.Test/ReleaseIntegrationTest.cs @@ -1,4 +1,5 @@ -using FluentAssertions; +using System.Threading; +using FluentAssertions; using NUnit.Framework; using NzbDrone.Api.Indexers; @@ -10,10 +11,13 @@ namespace NzbDrone.Integration.Test [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)); } diff --git a/NzbDrone.ncrunchsolution b/NzbDrone.ncrunchsolution index 098d74afe..28e2de798 100644 --- a/NzbDrone.ncrunchsolution +++ b/NzbDrone.ncrunchsolution @@ -7,7 +7,7 @@ Disabled Disabled Disabled - 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= + 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 \ No newline at end of file