Scheduled Tasks should work as long as they are registered.

This commit is contained in:
kay.one 2013-05-08 23:38:20 -07:00
parent fa8f67d7fe
commit 32431540c5
21 changed files with 253 additions and 106 deletions

View File

@ -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 @@ namespace NzbDrone.App.Test
[Test]
public void should_be_able_to_resolve_event_handlers()
{
MainAppContainerBuilder.BuildContainer().Resolve<IEnumerable<IProcessMessage>>().Should().NotBeEmpty();
MainAppContainerBuilder.BuildContainer().ResolveAll<IEnumerable<IProcessMessage>>().Should().NotBeEmpty();
}
[Test]
public void should_be_able_to_resolve_indexers()
{
@ -50,5 +54,17 @@ namespace NzbDrone.App.Test
executor.Should().NotBeNull();
executor.Should().BeAssignableTo<IExecute<RssSyncCommand>>();
}
[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<IHandle<ApplicationShutdownRequested>>().OfType<Scheduler>().Single();
var second = container.ResolveAll<IHandle<ApplicationShutdownRequested>>().OfType<Scheduler>().Single();
first.Should().BeSameAs(second);
}
}
}

View File

@ -89,6 +89,7 @@
<Compile Include="DiskProviderFixture.cs" />
<Compile Include="EnviromentProviderTest.cs" />
<Compile Include="ProcessProviderTests.cs" />
<Compile Include="ServiceFactoryFixture.cs" />
<Compile Include="ServiceProviderTests.cs" />
<Compile Include="WebClientTests.cs" />
</ItemGroup>
@ -103,6 +104,10 @@
<Project>{F2BE0FDF-6E47-4827-A420-DD4EF82407F8}</Project>
<Name>NzbDrone.Common</Name>
</ProjectReference>
<ProjectReference Include="..\NzbDrone.Core\NzbDrone.Core.csproj">
<Project>{FF5EE3B6-913B-47CE-9CEB-11C51B4E1205}</Project>
<Name>NzbDrone.Core</Name>
</ProjectReference>
<ProjectReference Include="..\NzbDrone.Test.Common\NzbDrone.Test.Common.csproj">
<Project>{CADDFCE0-7509-4430-8364-2074E1EEFCA2}</Project>
<Name>NzbDrone.Test.Common</Name>
@ -111,6 +116,10 @@
<Project>{FAFB5948-A222-4CF6-AD14-026BE7564802}</Project>
<Name>NzbDrone.Test.Dummy</Name>
</ProjectReference>
<ProjectReference Include="..\NzbDrone\NzbDrone.csproj">
<Project>{D12F7F2F-8A3C-415F-88FA-6DD061A84869}</Project>
<Name>NzbDrone</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />

View File

@ -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<ServiceFactory>
{
[SetUp]
public void setup()
{
Mocker.SetConstant(MainAppContainerBuilder.BuildContainer());
}
[Test]
public void event_handlers_should_be_unique()
{
var handlers = Subject.BuildAll<IHandle<ApplicationShutdownRequested>>()
.Select(c => c.GetType().FullName);
handlers.Should().OnlyHaveUniqueItems();
}
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace NzbDrone.Common.Composition
{
public class SingletonAttribute : Attribute
{
}
}

View File

@ -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 @@ namespace NzbDrone.Common
}
if (implementations.Count == 1)
{
Container.Register(contractType, implementations.Single()).AsMultiInstance();
if (implementations.Single().HasAttribute<SingletonAttribute>())
{
Container.Register(contractType, implementations.Single()).AsSingleton();
}
else
{
Container.Register(contractType, implementations.Single()).AsMultiInstance();
}
Container.RegisterMultiple(contractType, implementations).AsMultiInstance();
}
else

View File

@ -8,7 +8,7 @@ namespace NzbDrone.Common.Messaging
{
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);

View File

@ -79,6 +79,7 @@
<Compile Include="Cache\Cached.cs" />
<Compile Include="Cache\CacheManger.cs" />
<Compile Include="Cache\ICached.cs" />
<Compile Include="Composition\Class1.cs" />
<Compile Include="ContainerBuilderBase.cs" />
<Compile Include="EnsureThat\Ensure.cs" />
<Compile Include="EnsureThat\EnsureBoolExtensions.cs" />

View File

@ -56,5 +56,10 @@ namespace NzbDrone.Common.Reflection
return (T)attribute;
}
public static bool HasAttribute<TAttribute>(this Type type)
{
return type.GetCustomAttributes(typeof(TAttribute), true).Any();
}
}
}

View File

@ -28,7 +28,7 @@ namespace NzbDrone.Common
public IEnumerable<T> BuildAll<T>() where T : class
{
return _container.ResolveAll<T>();
return _container.ResolveAll<T>().GroupBy(c => c.GetType().FullName).Select(g => g.First());
}
public object Build(Type contract)

View File

@ -12,15 +12,15 @@ namespace NzbDrone.Core.Test.Datastore
[TestFixture]
public class
BasicRepositoryFixture : DbTest<BasicRepository<JobDefinition>, JobDefinition>
BasicRepositoryFixture : DbTest<BasicRepository<ScheduledTask>, ScheduledTask>
{
private JobDefinition _basicType;
private ScheduledTask _basicType;
[SetUp]
public void Setup()
{
_basicType = Builder<JobDefinition>
_basicType = Builder<ScheduledTask>
.CreateNew()
.With(c => c.Id = 0)
.Build();
@ -33,6 +33,18 @@ namespace NzbDrone.Core.Test.Datastore
Subject.All().Should().HaveCount(1);
}
[Test]
public void purge_should_delete_all()
{
Subject.InsertMany(Builder<ScheduledTask>.CreateListOfSize(10).BuildListOfNew());
AllStoredModels.Should().HaveCount(10);
Subject.Purge();
AllStoredModels.Should().BeEmpty();
}
[Test]
@ -62,6 +74,12 @@ namespace NzbDrone.Core.Test.Datastore
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()
{

View File

@ -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 @@ using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Datastore
{
[TestFixture]
public class ObjectDatabaseFixture : DbTest<BasicRepository<JobDefinition>, JobDefinition>
public class DatabaseFixture : DbTest
{
private JobDefinition _sampleType;
[Test]
public void SingleOrDefault_should_return_null_on_empty_db()
{
Mocker.Resolve<IDatabase>()
.DataMapper.Query<Series>()
.SingleOrDefault(c => c.CleanTitle == "SomeTitle")
.Should()
.BeNull();
}
}
[TestFixture]
public class ObjectDatabaseFixture : DbTest<BasicRepository<ScheduledTask>, ScheduledTask>
{
private ScheduledTask _sampleType;
[SetUp]
public void SetUp()
{
_sampleType = Builder<JobDefinition>
_sampleType = Builder<ScheduledTask>
.CreateNew()
.With(s => s.Id = 0)
.Build();
@ -31,7 +44,7 @@ namespace NzbDrone.Core.Test.Datastore
public void should_be_able_to_write_to_database()
{
Subject.Insert(_sampleType);
Db.All<JobDefinition>().Should().HaveCount(1);
Db.All<ScheduledTask>().Should().HaveCount(1);
}
[Test]
@ -51,7 +64,7 @@ namespace NzbDrone.Core.Test.Datastore
[Test]
public void should_be_able_to_store_empty_list()
{
var series = new List<JobDefinition>();
var series = new List<ScheduledTask>();
Subject.InsertMany(series);
}
@ -70,19 +83,22 @@ namespace NzbDrone.Core.Test.Datastore
_sampleType.Id = 0;
Subject.Insert(_sampleType);
Db.All<JobDefinition>().Should().HaveCount(1);
Db.All<ScheduledTask>().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<JobDefinition>();
var item = Db.All<ScheduledTask>();
item.Should().HaveCount(1);
item.First().Id.Should().NotBe(0);
@ -94,7 +110,7 @@ namespace NzbDrone.Core.Test.Datastore
public void should_be_able_to_find_object_by_id()
{
Subject.Insert(_sampleType);
var item = Db.All<JobDefinition>().Single(c => c.Id == _sampleType.Id);
var item = Db.All<ScheduledTask>().Single(c => c.Id == _sampleType.Id);
item.Id.Should().NotBe(0);
item.Id.Should().Be(_sampleType.Id);
@ -104,7 +120,7 @@ namespace NzbDrone.Core.Test.Datastore
[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 @@ namespace NzbDrone.Core.Test.Datastore
Subject.SetFields(childModel, t => t.Name);
Db.All<JobDefinition>().Single().Name.Should().Be("A");
Db.All<JobDefinition>().Single().Interval.Should().Be(12);
Db.All<ScheduledTask>().Single().Name.Should().Be("A");
Db.All<ScheduledTask>().Single().Interval.Should().Be(12);
}
[Test]
@ -128,7 +144,7 @@ namespace NzbDrone.Core.Test.Datastore
var rootFolder = Db.Insert(new RootFolders.RootFolder() { Path = "C:\test" });
var series = Builder<Series>.CreateNew()
.With(c=>c.RootFolderId = rootFolder.Id)
.With(c => c.RootFolderId = rootFolder.Id)
.BuildNew();
Db.Insert(series);

View File

@ -16,6 +16,9 @@ namespace NzbDrone.Core.DataAugmentation.Scene
IHandleAsync<ApplicationStartedEvent>,
IExecute<UpdateSceneMappingCommand>
{
private static readonly object mutex = new object();
private readonly ISceneMappingRepository _repository;
private readonly ISceneMappingProxy _sceneMappingProxy;
private readonly Logger _logger;
@ -62,15 +65,18 @@ namespace NzbDrone.Core.DataAugmentation.Scene
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)

View File

@ -91,11 +91,10 @@ namespace NzbDrone.Core.Datastore.Migration
.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()

View File

@ -33,7 +33,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<RootFolder>().RegisterModel("RootFolders").Ignore(r => r.FreeSpace);
Mapper.Entity<IndexerDefinition>().RegisterModel("IndexerDefinitions");
Mapper.Entity<JobDefinition>().RegisterModel("JobDefinitions");
Mapper.Entity<ScheduledTask>().RegisterModel("ScheduledTasks");
Mapper.Entity<ExternalNotificationDefinition>().RegisterModel("ExternalNotificationDefinitions");
Mapper.Entity<SceneMapping>().RegisterModel("SceneMappings");

View File

@ -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<JobDefinition>
public interface IScheduledTaskRepository : IBasicRepository<ScheduledTask>
{
IList<JobDefinition> GetPendingJobs();
JobDefinition GetDefinition(Type type);
IList<ScheduledTask> GetPendingJobs();
ScheduledTask GetDefinition(Type type);
}
public class JobRepository : BasicRepository<JobDefinition>, IJobRepository, IHandle<ApplicationStartedEvent>
{
private readonly Logger _logger;
public JobRepository(IDatabase database, Logger logger, IMessageAggregator messageAggregator)
public class ScheduledTaskRepository : BasicRepository<ScheduledTask>, 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<JobDefinition> GetPendingJobs()
public IList<ScheduledTask> 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);
}*/
}
}
}

View File

@ -3,11 +3,10 @@ using NzbDrone.Core.Datastore;
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; }
}
}

View File

@ -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<ApplicationStartedEvent>,
IHandle<ApplicationShutdownRequested>
{
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 @@ namespace NzbDrone.Core.Jobs
public void Handle(ApplicationShutdownRequested message)
{
_timer.Stop();
Timer.Stop();
}
}
}

View File

@ -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<ScheduledTask> GetPending();
}
public class TaskManager : IHandle<ApplicationStartedEvent>, ITaskManager
{
private readonly IScheduledTaskRepository _scheduledTaskRepository;
private readonly Logger _logger;
public TaskManager(IScheduledTaskRepository scheduledTaskRepository, Logger logger)
{
_scheduledTaskRepository = scheduledTaskRepository;
_logger = logger;
}
public IList<ScheduledTask> 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);
}
}
}
}
}

View File

@ -244,6 +244,7 @@
<Compile Include="Indexers\IndexerSettingUpdatedEvent.cs" />
<Compile Include="Indexers\IndexerWithSetting.cs" />
<Compile Include="Indexers\RssSyncCommand.cs" />
<Compile Include="Jobs\TaskManager.cs" />
<Compile Include="Lifecycle\ApplicationShutdownRequested.cs" />
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" />
@ -285,10 +286,10 @@
<Compile Include="Indexers\Wombles\Wombles.cs" />
<Compile Include="Indexers\Wombles\WomblesParser.cs" />
<Compile Include="Instrumentation\LogRepository.cs" />
<Compile Include="Jobs\JobDefinition.cs" />
<Compile Include="Jobs\ScheduledTask.cs" />
<Compile Include="Jobs\JobQueueItem.cs" />
<Compile Include="Jobs\JobRepository.cs" />
<Compile Include="Jobs\JobTimer.cs" />
<Compile Include="Jobs\Scheduler.cs" />
<Compile Include="Lifecycle\ApplicationStartedEvent.cs" />
<Compile Include="MediaCover\MediaCover.cs" />
<Compile Include="MediaFiles\EpisodeFileMovingService.cs" />

View File

@ -2,6 +2,7 @@
<FileVersion>1</FileVersion>
<AutoEnableOnStartup>False</AutoEnableOnStartup>
<AllowParallelTestExecution>true</AllowParallelTestExecution>
<AllowTestsToRunInParallelWithThemselves>true</AllowTestsToRunInParallelWithThemselves>
<FrameworkUtilisationTypeForNUnit>UseDynamicAnalysis</FrameworkUtilisationTypeForNUnit>
<FrameworkUtilisationTypeForGallio>Disabled</FrameworkUtilisationTypeForGallio>
<FrameworkUtilisationTypeForMSpec>Disabled</FrameworkUtilisationTypeForMSpec>

View File

@ -27,6 +27,6 @@
<rules>
<logger name="*" minlevel="Trace" writeTo="consoleLogger"/>
<logger name="*" minlevel="Off" writeTo="udpTarget"/>
<logger name="*" minlevel="Trace" writeTo="rollingFileLogger"/>
<logger name="*" minlevel="Warn" writeTo="rollingFileLogger"/>
</rules>
</nlog>