diff --git a/NzbDrone.App.Test/ContainerFixture.cs b/NzbDrone.App.Test/ContainerFixture.cs index 59098aa77..6b9e28fed 100644 --- a/NzbDrone.App.Test/ContainerFixture.cs +++ b/NzbDrone.App.Test/ContainerFixture.cs @@ -1,13 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using NUnit.Framework; using NzbDrone.Common; using NzbDrone.Common.Messaging; using NzbDrone.Core.Download; using NzbDrone.Core.Indexers; +using NzbDrone.Core.Jobs; +using NzbDrone.Core.Lifecycle; using NzbDrone.Test.Common; using FluentAssertions; -using TinyIoC; +using System.Linq; namespace NzbDrone.App.Test { @@ -17,9 +18,12 @@ public class ContainerFixture : TestBase [Test] public void should_be_able_to_resolve_event_handlers() { - MainAppContainerBuilder.BuildContainer().Resolve>().Should().NotBeEmpty(); + MainAppContainerBuilder.BuildContainer().ResolveAll>().Should().NotBeEmpty(); } + + + [Test] public void should_be_able_to_resolve_indexers() { @@ -50,5 +54,17 @@ public void should_resolve_command_executor_by_name() executor.Should().NotBeNull(); executor.Should().BeAssignableTo>(); } + + [Test] + [Ignore("need to fix this at some point")] + public void should_return_same_instance_of_singletons() + { + var container = MainAppContainerBuilder.BuildContainer(); + + var first = container.ResolveAll>().OfType().Single(); + var second = container.ResolveAll>().OfType().Single(); + + first.Should().BeSameAs(second); + } } } \ No newline at end of file diff --git a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index b52010006..aaaacf101 100644 --- a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -89,6 +89,7 @@ + @@ -103,6 +104,10 @@ {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} NzbDrone.Common + + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} + NzbDrone.Core + {CADDFCE0-7509-4430-8364-2074E1EEFCA2} NzbDrone.Test.Common @@ -111,6 +116,10 @@ {FAFB5948-A222-4CF6-AD14-026BE7564802} NzbDrone.Test.Dummy + + {D12F7F2F-8A3C-415F-88FA-6DD061A84869} + NzbDrone + diff --git a/NzbDrone.Common.Test/ServiceFactoryFixture.cs b/NzbDrone.Common.Test/ServiceFactoryFixture.cs new file mode 100644 index 000000000..9241c3fe1 --- /dev/null +++ b/NzbDrone.Common.Test/ServiceFactoryFixture.cs @@ -0,0 +1,29 @@ +using System.Linq; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Test.Common; + +namespace NzbDrone.Common.Test +{ + [TestFixture] + public class ServiceFactoryFixture : TestBase + { + [SetUp] + public void setup() + { + Mocker.SetConstant(MainAppContainerBuilder.BuildContainer()); + } + + + [Test] + public void event_handlers_should_be_unique() + { + var handlers = Subject.BuildAll>() + .Select(c => c.GetType().FullName); + + handlers.Should().OnlyHaveUniqueItems(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Composition/Class1.cs b/NzbDrone.Common/Composition/Class1.cs new file mode 100644 index 000000000..bf9272525 --- /dev/null +++ b/NzbDrone.Common/Composition/Class1.cs @@ -0,0 +1,8 @@ +using System; + +namespace NzbDrone.Common.Composition +{ + public class SingletonAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/NzbDrone.Common/ContainerBuilderBase.cs b/NzbDrone.Common/ContainerBuilderBase.cs index ccde67f0b..a648c6a36 100644 --- a/NzbDrone.Common/ContainerBuilderBase.cs +++ b/NzbDrone.Common/ContainerBuilderBase.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using NzbDrone.Common.Composition; using NzbDrone.Common.Messaging; using TinyIoC; +using NzbDrone.Common.Reflection; namespace NzbDrone.Common { @@ -58,7 +60,15 @@ private void AutoRegisterImplementations(Type contractType) } if (implementations.Count == 1) { - Container.Register(contractType, implementations.Single()).AsMultiInstance(); + if (implementations.Single().HasAttribute()) + { + Container.Register(contractType, implementations.Single()).AsSingleton(); + } + else + { + Container.Register(contractType, implementations.Single()).AsMultiInstance(); + } + Container.RegisterMultiple(contractType, implementations).AsMultiInstance(); } else diff --git a/NzbDrone.Common/Messaging/MessageExtensions.cs b/NzbDrone.Common/Messaging/MessageExtensions.cs index e5f9ec2fe..302aad869 100644 --- a/NzbDrone.Common/Messaging/MessageExtensions.cs +++ b/NzbDrone.Common/Messaging/MessageExtensions.cs @@ -8,7 +8,7 @@ public static string GetExecutorName(this Type commandType) { if (!typeof(ICommand).IsAssignableFrom(commandType)) { - throw new ArgumentException("commandType must implement IExecute"); + throw new ArgumentException("commandType must implement ICommand"); } return string.Format("I{0}Executor", commandType.Name); diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index f31a6a3a7..c69f2f66c 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -79,6 +79,7 @@ + diff --git a/NzbDrone.Common/Reflection/ReflectionExtensions.cs b/NzbDrone.Common/Reflection/ReflectionExtensions.cs index d772f5765..a19408662 100644 --- a/NzbDrone.Common/Reflection/ReflectionExtensions.cs +++ b/NzbDrone.Common/Reflection/ReflectionExtensions.cs @@ -56,5 +56,10 @@ public static T GetAttribute(this MemberInfo member, bool isRequired = true) return (T)attribute; } + + public static bool HasAttribute(this Type type) + { + return type.GetCustomAttributes(typeof(TAttribute), true).Any(); + } } } \ No newline at end of file diff --git a/NzbDrone.Common/ServiceFactory.cs b/NzbDrone.Common/ServiceFactory.cs index 0a47da334..29e79912a 100644 --- a/NzbDrone.Common/ServiceFactory.cs +++ b/NzbDrone.Common/ServiceFactory.cs @@ -28,7 +28,7 @@ public T Build() where T : class public IEnumerable BuildAll() where T : class { - return _container.ResolveAll(); + return _container.ResolveAll().GroupBy(c => c.GetType().FullName).Select(g => g.First()); } public object Build(Type contract) diff --git a/NzbDrone.Core.Test/Datastore/BasicRepositoryFixture.cs b/NzbDrone.Core.Test/Datastore/BasicRepositoryFixture.cs index 27d0813c1..e0045bd7d 100644 --- a/NzbDrone.Core.Test/Datastore/BasicRepositoryFixture.cs +++ b/NzbDrone.Core.Test/Datastore/BasicRepositoryFixture.cs @@ -12,15 +12,15 @@ namespace NzbDrone.Core.Test.Datastore [TestFixture] public class - BasicRepositoryFixture : DbTest, JobDefinition> + BasicRepositoryFixture : DbTest, ScheduledTask> { - private JobDefinition _basicType; + private ScheduledTask _basicType; [SetUp] public void Setup() { - _basicType = Builder + _basicType = Builder .CreateNew() .With(c => c.Id = 0) .Build(); @@ -33,6 +33,18 @@ public void should_be_able_to_add() Subject.All().Should().HaveCount(1); } + [Test] + public void purge_should_delete_all() + { + Subject.InsertMany(Builder.CreateListOfSize(10).BuildListOfNew()); + + AllStoredModels.Should().HaveCount(10); + + Subject.Purge(); + + AllStoredModels.Should().BeEmpty(); + + } [Test] @@ -62,6 +74,12 @@ public void should_be_able_to_get_single() Subject.SingleOrDefault().Should().NotBeNull(); } + [Test] + public void single_or_default_on_empty_table_should_return_null() + { + Subject.SingleOrDefault().Should().BeNull(); + } + [Test] public void getting_model_with_invalid_id_should_throw() { diff --git a/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs b/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs index 7be8b7bd2..d12d414bb 100644 --- a/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs +++ b/NzbDrone.Core.Test/Datastore/ObjectDatabaseFixture.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Data; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; @@ -12,15 +11,29 @@ namespace NzbDrone.Core.Test.Datastore { - [TestFixture] - public class ObjectDatabaseFixture : DbTest, JobDefinition> + + public class DatabaseFixture : DbTest { - private JobDefinition _sampleType; + [Test] + public void SingleOrDefault_should_return_null_on_empty_db() + { + Mocker.Resolve() + .DataMapper.Query() + .SingleOrDefault(c => c.CleanTitle == "SomeTitle") + .Should() + .BeNull(); + } + } + + [TestFixture] + public class ObjectDatabaseFixture : DbTest, ScheduledTask> + { + private ScheduledTask _sampleType; [SetUp] public void SetUp() { - _sampleType = Builder + _sampleType = Builder .CreateNew() .With(s => s.Id = 0) .Build(); @@ -31,7 +44,7 @@ public void SetUp() public void should_be_able_to_write_to_database() { Subject.Insert(_sampleType); - Db.All().Should().HaveCount(1); + Db.All().Should().HaveCount(1); } [Test] @@ -51,7 +64,7 @@ public void update_item_with_root_index_0_should_faile() [Test] public void should_be_able_to_store_empty_list() { - var series = new List(); + var series = new List(); Subject.InsertMany(series); } @@ -70,19 +83,22 @@ public void new_object_should_get_new_id() _sampleType.Id = 0; Subject.Insert(_sampleType); - Db.All().Should().HaveCount(1); + Db.All().Should().HaveCount(1); _sampleType.Id.Should().Be(1); } + + + [Test] public void should_have_id_when_returned_from_database() { _sampleType.Id = 0; Subject.Insert(_sampleType); - var item = Db.All(); + var item = Db.All(); item.Should().HaveCount(1); item.First().Id.Should().NotBe(0); @@ -94,7 +110,7 @@ public void should_have_id_when_returned_from_database() public void should_be_able_to_find_object_by_id() { Subject.Insert(_sampleType); - var item = Db.All().Single(c => c.Id == _sampleType.Id); + var item = Db.All().Single(c => c.Id == _sampleType.Id); item.Id.Should().NotBe(0); item.Id.Should().Be(_sampleType.Id); @@ -104,7 +120,7 @@ public void should_be_able_to_find_object_by_id() [Test] public void set_fields_should_only_update_selected_filed() { - var childModel = new JobDefinition + var childModel = new ScheduledTask { Name = "Address", Interval = 12 @@ -117,8 +133,8 @@ public void set_fields_should_only_update_selected_filed() Subject.SetFields(childModel, t => t.Name); - Db.All().Single().Name.Should().Be("A"); - Db.All().Single().Interval.Should().Be(12); + Db.All().Single().Name.Should().Be("A"); + Db.All().Single().Interval.Should().Be(12); } [Test] @@ -128,7 +144,7 @@ public void should_load_lazy_objects() var rootFolder = Db.Insert(new RootFolders.RootFolder() { Path = "C:\test" }); var series = Builder.CreateNew() - .With(c=>c.RootFolderId = rootFolder.Id) + .With(c => c.RootFolderId = rootFolder.Id) .BuildNew(); Db.Insert(series); diff --git a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs index 7198d8392..2613b7055 100644 --- a/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs +++ b/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs @@ -16,6 +16,9 @@ public class SceneMappingService : ISceneMappingService, IHandleAsync, IExecute { + + private static readonly object mutex = new object(); + private readonly ISceneMappingRepository _repository; private readonly ISceneMappingProxy _sceneMappingProxy; private readonly Logger _logger; @@ -62,15 +65,18 @@ private void UpdateMappings() try { var mappings = _sceneMappingProxy.Fetch(); - - if (mappings.Any()) + + lock (mutex) { - _repository.Purge(); - _repository.InsertMany(mappings); - } - else - { - _logger.Warn("Received empty list of mapping. will not update."); + if (mappings.Any()) + { + _repository.Purge(); + _repository.InsertMany(mappings); + } + else + { + _logger.Warn("Received empty list of mapping. will not update."); + } } } catch (Exception ex) diff --git a/NzbDrone.Core/Datastore/Migration/Migration20130324.cs b/NzbDrone.Core/Datastore/Migration/Migration20130324.cs index 3feca052d..a581b751a 100644 --- a/NzbDrone.Core/Datastore/Migration/Migration20130324.cs +++ b/NzbDrone.Core/Datastore/Migration/Migration20130324.cs @@ -91,11 +91,10 @@ protected override void MainDbUpgrade() .WithColumn("Type").AsString().Unique() .WithColumn("Name").AsString().Unique(); - Create.TableForModel("JobDefinitions") + Create.TableForModel("ScheduledTasks") .WithColumn("Name").AsString().Unique() .WithColumn("Interval").AsInt32() - .WithColumn("LastExecution").AsDateTime() - .WithColumn("Success").AsBoolean(); + .WithColumn("LastExecution").AsDateTime(); Create.TableForModel("IndexerDefinitions") .WithColumn("Enable").AsBoolean() diff --git a/NzbDrone.Core/Datastore/TableMapping.cs b/NzbDrone.Core/Datastore/TableMapping.cs index b824d74b8..fe6f87ffc 100644 --- a/NzbDrone.Core/Datastore/TableMapping.cs +++ b/NzbDrone.Core/Datastore/TableMapping.cs @@ -33,7 +33,7 @@ public static void Map() Mapper.Entity().RegisterModel("RootFolders").Ignore(r => r.FreeSpace); Mapper.Entity().RegisterModel("IndexerDefinitions"); - Mapper.Entity().RegisterModel("JobDefinitions"); + Mapper.Entity().RegisterModel("ScheduledTasks"); Mapper.Entity().RegisterModel("ExternalNotificationDefinitions"); Mapper.Entity().RegisterModel("SceneMappings"); diff --git a/NzbDrone.Core/Jobs/JobRepository.cs b/NzbDrone.Core/Jobs/JobRepository.cs index cd8299aee..26d468a8b 100644 --- a/NzbDrone.Core/Jobs/JobRepository.cs +++ b/NzbDrone.Core/Jobs/JobRepository.cs @@ -1,78 +1,35 @@ using System; using System.Collections.Generic; using System.Linq; -using NLog; using NzbDrone.Common.Messaging; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Indexers; -using NzbDrone.Core.Lifecycle; -using NzbDrone.Core.Providers; namespace NzbDrone.Core.Jobs { - public interface IJobRepository : IBasicRepository + public interface IScheduledTaskRepository : IBasicRepository { - IList GetPendingJobs(); - JobDefinition GetDefinition(Type type); + IList GetPendingJobs(); + ScheduledTask GetDefinition(Type type); } - public class JobRepository : BasicRepository, IJobRepository, IHandle - { - private readonly Logger _logger; - public JobRepository(IDatabase database, Logger logger, IMessageAggregator messageAggregator) + public class ScheduledTaskRepository : BasicRepository, IScheduledTaskRepository + { + + public ScheduledTaskRepository(IDatabase database, IMessageAggregator messageAggregator) : base(database, messageAggregator) { - _logger = logger; } - public JobDefinition GetDefinition(Type type) + public ScheduledTask GetDefinition(Type type) { return Query.Single(c => c.Name == type.FullName); } - public IList GetPendingJobs() + public IList GetPendingJobs() { return Query.Where(c => c.Interval != 0).ToList().Where(c => c.LastExecution < DateTime.Now.AddMinutes(-c.Interval)).ToList(); } - - public void Handle(ApplicationStartedEvent message) - { - /* var currentJobs = All().ToList(); - - - var timers = new[] - { - new JobDefinition{ Interval = 25, Name = typeof(RssSyncCommand).FullName}, - new JobDefinition{ Interval = 24*60, Name = typeof(UpdateXemMappings).FullName} - }; - - - _logger.Debug("Initializing jobs. Available: {0} Existing:{1}", timers.Count(), currentJobs.Count()); - - foreach (var job in currentJobs) - { - if (!timers.Any(c => c.Name == job.Name)) - { - _logger.Debug("Removing job from database '{0}'", job.Name); - Delete(job.Id); - } - } - - foreach (var job in timers) - { - var currentDefinition = currentJobs.SingleOrDefault(c => c.Name == job.GetType().ToString()); - - if (currentDefinition == null) - { - currentDefinition = job; - } - - currentDefinition.Interval = job.Interval; - - Upsert(currentDefinition); - }*/ - } } } diff --git a/NzbDrone.Core/Jobs/JobDefinition.cs b/NzbDrone.Core/Jobs/ScheduledTask.cs similarity index 72% rename from NzbDrone.Core/Jobs/JobDefinition.cs rename to NzbDrone.Core/Jobs/ScheduledTask.cs index 44d81eed4..5e3a4e21d 100644 --- a/NzbDrone.Core/Jobs/JobDefinition.cs +++ b/NzbDrone.Core/Jobs/ScheduledTask.cs @@ -3,11 +3,10 @@ namespace NzbDrone.Core.Jobs { - public class JobDefinition : ModelBase + public class ScheduledTask : ModelBase { public String Name { get; set; } public Int32 Interval { get; set; } public DateTime LastExecution { get; set; } - public Boolean Success { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Jobs/JobTimer.cs b/NzbDrone.Core/Jobs/Scheduler.cs similarity index 55% rename from NzbDrone.Core/Jobs/JobTimer.cs rename to NzbDrone.Core/Jobs/Scheduler.cs index 19286dfbf..c53bd6380 100644 --- a/NzbDrone.Core/Jobs/JobTimer.cs +++ b/NzbDrone.Core/Jobs/Scheduler.cs @@ -1,39 +1,40 @@ using System; using System.Timers; +using NzbDrone.Common.Composition; using NzbDrone.Common.Messaging; using NzbDrone.Core.Lifecycle; namespace NzbDrone.Core.Jobs { - public class JobTimer : + [Singleton] + public class Scheduler : IHandle, IHandle { - private readonly IJobRepository _jobRepository; + private readonly ITaskManager _taskManager; private readonly IMessageAggregator _messageAggregator; - private readonly Timer _timer; + private static readonly Timer Timer = new Timer(); - public JobTimer(IJobRepository jobRepository, IMessageAggregator messageAggregator) + public Scheduler(ITaskManager taskManager, IMessageAggregator messageAggregator) { - _jobRepository = jobRepository; + _taskManager = taskManager; _messageAggregator = messageAggregator; - _timer = new Timer(); } public void Handle(ApplicationStartedEvent message) { - _timer.Interval = 1000 * 30; - _timer.Elapsed += (o, args) => ExecuteCommands(); - //_timer.Start(); + Timer.Interval = 1000 * 30; + Timer.Elapsed += (o, args) => ExecuteCommands(); + Timer.Start(); } private void ExecuteCommands() { - var jobs = _jobRepository.GetPendingJobs(); + var tasks = _taskManager.GetPending(); - foreach (var jobDefinition in jobs) + foreach (var task in tasks) { - var commandType = Type.GetType(jobDefinition.Name); + var commandType = Type.GetType(task.Name); var command = (ICommand)Activator.CreateInstance(commandType); _messageAggregator.PublishCommand(command); @@ -42,7 +43,9 @@ private void ExecuteCommands() public void Handle(ApplicationShutdownRequested message) { - _timer.Stop(); + Timer.Stop(); } } + + } \ No newline at end of file diff --git a/NzbDrone.Core/Jobs/TaskManager.cs b/NzbDrone.Core/Jobs/TaskManager.cs new file mode 100644 index 000000000..ab4b33c66 --- /dev/null +++ b/NzbDrone.Core/Jobs/TaskManager.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Providers; + +namespace NzbDrone.Core.Jobs +{ + public interface ITaskManager + { + IList GetPending(); + } + + public class TaskManager : IHandle, ITaskManager + { + private readonly IScheduledTaskRepository _scheduledTaskRepository; + private readonly Logger _logger; + + public TaskManager(IScheduledTaskRepository scheduledTaskRepository, Logger logger) + { + _scheduledTaskRepository = scheduledTaskRepository; + _logger = logger; + } + + + public IList GetPending() + { + return _scheduledTaskRepository.GetPendingJobs(); + } + + public void Handle(ApplicationStartedEvent message) + { + var defaultTasks = new[] + { + new ScheduledTask{ Interval = 25, Name = typeof(RssSyncCommand).FullName}, + new ScheduledTask{ Interval = 24*60, Name = typeof(UpdateXemMappings).FullName} + }; + + var currentTasks = _scheduledTaskRepository.All(); + + + _logger.Debug("Initializing jobs. Available: {0} Existing:{1}", defaultTasks.Count(), currentTasks.Count()); + + + foreach (var job in currentTasks) + { + if (!defaultTasks.Any(c => c.Name == job.Name)) + { + _logger.Debug("Removing job from database '{0}'", job.Name); + _scheduledTaskRepository.Delete(job.Id); + } + } + + foreach (var defaultTask in defaultTasks) + { + var currentDefinition = currentTasks.SingleOrDefault(c => c.Name == defaultTask.Name); + + if (currentDefinition == null) + { + currentDefinition = defaultTask; + _scheduledTaskRepository.Upsert(currentDefinition); + } + + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index d29e7cd9d..df8204e74 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -244,6 +244,7 @@ + @@ -285,10 +286,10 @@ - + - + diff --git a/NzbDrone.ncrunchsolution b/NzbDrone.ncrunchsolution index 6cb47a29a..444b34b1d 100644 --- a/NzbDrone.ncrunchsolution +++ b/NzbDrone.ncrunchsolution @@ -2,6 +2,7 @@ 1 False true + true UseDynamicAnalysis Disabled Disabled diff --git a/NzbDrone/NLog.config b/NzbDrone/NLog.config index a5d313b44..f74e2d081 100644 --- a/NzbDrone/NLog.config +++ b/NzbDrone/NLog.config @@ -27,6 +27,6 @@ - + \ No newline at end of file