Added completely awesome JobProvider. extremely easy to do async/timer tasks with ui status/notification already plugged in.

pull/4/head
kay.one 13 years ago
parent b86dac57e1
commit 9028e498ca

@ -30,8 +30,9 @@ namespace AutoMoq
public virtual T Resolve<T>() public virtual T Resolve<T>()
{ {
ResolveType = typeof (T); ResolveType = typeof(T);
var result = container.Resolve<T>(); var result = container.Resolve<T>();
SetConstant(result);
ResolveType = null; ResolveType = null;
return result; return result;
} }
@ -61,7 +62,7 @@ namespace AutoMoq
registeredMocks.Add(type, mock); registeredMocks.Add(type, mock);
} }
public virtual void SetConstant<T>(T instance) where T : class public virtual void SetConstant<T>(T instance)
{ {
container.RegisterInstance(instance); container.RegisterInstance(instance);
SetMock(instance.GetType(), null); SetMock(instance.GetType(), null);
@ -125,7 +126,7 @@ namespace AutoMoq
private Mock<T> TheRegisteredMockForThisType<T>(Type type) where T : class private Mock<T> TheRegisteredMockForThisType<T>(Type type) where T : class
{ {
return (Mock<T>) registeredMocks.Where(x => x.Key == type).First().Value; return (Mock<T>)registeredMocks.Where(x => x.Key == type).First().Value;
} }
private void CreateANewMockAndRegisterIt<T>(Type type, MockBehavior behavior) where T : class private void CreateANewMockAndRegisterIt<T>(Type type, MockBehavior behavior) where T : class
@ -142,7 +143,7 @@ namespace AutoMoq
private static Type GetTheMockType<T>() where T : class private static Type GetTheMockType<T>() where T : class
{ {
return typeof (T); return typeof(T);
} }
#endregion #endregion

@ -87,5 +87,25 @@ namespace NzbDrone.Core.Test
//Assert //Assert
} }
[Test]
public void Add_daily_show_episodes()
{
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
mocker.Resolve<TvDbProvider>();
const int tvDbSeriesId = 71256;
//act
var seriesProvider = mocker.Resolve<SeriesProvider>();
seriesProvider.AddSeries("c:\\test\\", tvDbSeriesId, 0);
var episodeProvider = mocker.Resolve<EpisodeProvider>();
episodeProvider.RefreshEpisodeInfo(tvDbSeriesId);
//assert
var episodes = episodeProvider.GetEpisodeBySeries(tvDbSeriesId);
Assert.IsNotEmpty(episodes);
}
} }
} }

@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Threading;
using AutoMoq;
using MbUnit.Framework;
using NzbDrone.Core.Model.Notification;
using NzbDrone.Core.Providers.Jobs;
namespace NzbDrone.Core.Test
{
[TestFixture]
// ReSharper disable InconsistentNaming
public class JobProviderTest
{
[Test]
public void Run_Jobs()
{
IEnumerable<IJob> fakeTimers = new List<IJob> { new FakeJob() };
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<JobProvider>();
timerProvider.Initialize();
timerProvider.RunScheduled();
}
[Test]
//This test will confirm that the concurrency checks are rest
//after execution so the job can successfully run.
public void can_run_job_again()
{
IEnumerable<IJob> fakeTimers = new List<IJob> { new FakeJob() };
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<JobProvider>();
timerProvider.Initialize();
var firstRun = timerProvider.RunScheduled();
var secondRun = timerProvider.RunScheduled();
Assert.IsTrue(firstRun);
Assert.IsTrue(secondRun);
}
[Test]
//This test will confirm that the concurrency checks are rest
//after execution so the job can successfully run.
public void can_run_async_job_again()
{
IEnumerable<IJob> fakeTimers = new List<IJob> { new FakeJob() };
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<JobProvider>();
timerProvider.Initialize();
var firstRun = timerProvider.BeginExecute(typeof(FakeJob));
Thread.Sleep(2000);
var secondRun = timerProvider.BeginExecute(typeof(FakeJob));
Assert.IsTrue(firstRun);
Assert.IsTrue(secondRun);
}
[Test]
//This test will confirm that the concurrency checks are rest
//after execution so the job can successfully run.
public void can_run_two_jobs_at_the_same_time()
{
IEnumerable<IJob> fakeTimers = new List<IJob> { new SlowJob() };
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<JobProvider>();
timerProvider.Initialize();
bool firstRun = false;
bool secondRun = false;
var thread1 = new Thread(() => firstRun = timerProvider.RunScheduled());
thread1.Start();
Thread.Sleep(1000);
var thread2 = new Thread(() => secondRun = timerProvider.RunScheduled());
thread2.Start();
thread1.Join();
thread2.Join();
Assert.IsTrue(firstRun);
Assert.IsFalse(secondRun);
}
[Test]
public void Init_Jobs()
{
var fakeTimer = new FakeJob();
IEnumerable<IJob> fakeTimers = new List<IJob> { fakeTimer };
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<JobProvider>();
timerProvider.Initialize();
var timers = timerProvider.All();
//Assert
Assert.Count(1, timers);
Assert.AreEqual(fakeTimer.DefaultInterval, timers[0].Interval);
Assert.AreEqual(fakeTimer.Name, timers[0].Name);
Assert.AreEqual(fakeTimer.GetType().ToString(), timers[0].TypeName);
Assert.AreEqual(DateTime.MinValue, timers[0].LastExecution);
Assert.IsTrue(timers[0].Enable);
}
[Test]
public void Init_Timers_only_registers_once()
{
var repo = MockLib.GetEmptyRepository();
for (int i = 0; i < 2; i++)
{
var fakeTimer = new FakeJob();
IEnumerable<IJob> fakeTimers = new List<IJob> { fakeTimer };
var mocker = new AutoMoqer();
mocker.SetConstant(repo);
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<JobProvider>();
timerProvider.Initialize();
}
var mocker2 = new AutoMoqer();
mocker2.SetConstant(repo);
var assertTimerProvider = mocker2.Resolve<JobProvider>();
var timers = assertTimerProvider.All();
//Assert
Assert.Count(1, timers);
}
}
public class FakeJob : IJob
{
public string Name
{
get { return "FakeJob"; }
}
public int DefaultInterval
{
get { return 15; }
}
public void Start(ProgressNotification notification, int targetId)
{
throw new NotImplementedException();
}
}
public class SlowJob : IJob
{
public string Name
{
get { return "FakeJob"; }
}
public int DefaultInterval
{
get { return 15; }
}
public void Start(ProgressNotification notification, int targetId)
{
Thread.Sleep(10000);
}
}
}

@ -93,7 +93,7 @@
<Compile Include="AutoMoq\Unity\AutoMockingContainerExtension.cs"> <Compile Include="AutoMoq\Unity\AutoMockingContainerExtension.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="TimerProviderTest.cs" /> <Compile Include="JobProviderTest.cs" />
<Compile Include="SyncProviderTest.cs" /> <Compile Include="SyncProviderTest.cs" />
<Compile Include="RootDirProviderTest.cs" /> <Compile Include="RootDirProviderTest.cs" />
<Compile Include="IndexerProviderTest.cs" /> <Compile Include="IndexerProviderTest.cs" />

@ -1,4 +1,5 @@
using System.Threading; using System;
using System.Threading;
using MbUnit.Framework; using MbUnit.Framework;
using NzbDrone.Core.Repository.Quality; using NzbDrone.Core.Repository.Quality;

@ -1,110 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using AutoMoq;
using MbUnit.Framework;
using Moq;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Providers.Core;
using NzbDrone.Core.Providers.Timers;
namespace NzbDrone.Core.Test
{
[TestFixture]
// ReSharper disable InconsistentNaming
public class TimerProviderTest
{
[Test]
public void Run_Timers()
{
IEnumerable<ITimer> fakeTimers = new List<ITimer> { new FakeTimer() };
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<TimerProvider>();
timerProvider.Initialize();
timerProvider.Run();
}
[Test]
public void Init_Timers()
{
var fakeTimer = new FakeTimer();
IEnumerable<ITimer> fakeTimers = new List<ITimer> { fakeTimer };
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<TimerProvider>();
timerProvider.Initialize();
var timers = timerProvider.All();
//Assert
Assert.Count(1, timers);
Assert.AreEqual(fakeTimer.DefaultInterval, timers[0].Interval);
Assert.AreEqual(fakeTimer.Name, timers[0].Name);
Assert.AreEqual(fakeTimer.GetType().ToString(), timers[0].TypeName);
Assert.AreEqual(DateTime.MinValue, timers[0].LastExecution);
Assert.IsTrue(timers[0].Enable);
}
[Test]
public void Init_Timers_only_registers_once()
{
var repo = MockLib.GetEmptyRepository();
for (int i = 0; i < 2; i++)
{
var fakeTimer = new FakeTimer();
IEnumerable<ITimer> fakeTimers = new List<ITimer> { fakeTimer };
var mocker = new AutoMoqer();
mocker.SetConstant(repo);
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<TimerProvider>();
timerProvider.Initialize();
}
var mocker2 = new AutoMoqer();
mocker2.SetConstant(repo);
var assertTimerProvider = mocker2.Resolve<TimerProvider>();
var timers = assertTimerProvider.All();
//Assert
Assert.Count(1, timers);
}
}
public class FakeTimer : ITimer
{
public string Name
{
get { return "FakeTimer"; }
}
public int DefaultInterval
{
get { return 15; }
}
public void Start()
{
}
}
}

@ -10,7 +10,7 @@ using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Providers; using NzbDrone.Core.Providers;
using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Providers.Core;
using NzbDrone.Core.Providers.Indexer; using NzbDrone.Core.Providers.Indexer;
using NzbDrone.Core.Providers.Timers; using NzbDrone.Core.Providers.Jobs;
using NzbDrone.Core.Repository; using NzbDrone.Core.Repository;
using NzbDrone.Core.Repository.Quality; using NzbDrone.Core.Repository.Quality;
using SubSonic.DataProviders; using SubSonic.DataProviders;
@ -77,7 +77,7 @@ namespace NzbDrone.Core
dbProvider.Log = new NlogWriter(); dbProvider.Log = new NlogWriter();
_kernel.Bind<QualityProvider>().ToSelf().InSingletonScope(); _kernel.Bind<QualityProvider>().ToSelf().InSingletonScope();
_kernel.Bind<TvDbProvider>().ToSelf().InSingletonScope(); _kernel.Bind<TvDbProvider>().ToSelf().InTransientScope();
_kernel.Bind<HttpProvider>().ToSelf().InSingletonScope(); _kernel.Bind<HttpProvider>().ToSelf().InSingletonScope();
_kernel.Bind<SeriesProvider>().ToSelf().InSingletonScope(); _kernel.Bind<SeriesProvider>().ToSelf().InSingletonScope();
_kernel.Bind<SeasonProvider>().ToSelf().InSingletonScope(); _kernel.Bind<SeasonProvider>().ToSelf().InSingletonScope();
@ -96,7 +96,7 @@ namespace NzbDrone.Core
_kernel.Bind<NotificationProvider>().ToSelf().InSingletonScope(); _kernel.Bind<NotificationProvider>().ToSelf().InSingletonScope();
_kernel.Bind<LogProvider>().ToSelf().InSingletonScope(); _kernel.Bind<LogProvider>().ToSelf().InSingletonScope();
_kernel.Bind<MediaFileProvider>().ToSelf().InSingletonScope(); _kernel.Bind<MediaFileProvider>().ToSelf().InSingletonScope();
_kernel.Bind<TimerProvider>().ToSelf().InSingletonScope(); _kernel.Bind<JobProvider>().ToSelf().InSingletonScope();
_kernel.Bind<IndexerProvider>().ToSelf().InSingletonScope(); _kernel.Bind<IndexerProvider>().ToSelf().InSingletonScope();
_kernel.Bind<IRepository>().ToMethod( _kernel.Bind<IRepository>().ToMethod(
c => new SimpleRepository(dbProvider, SimpleRepositoryOptions.RunMigrations)).InSingletonScope(); c => new SimpleRepository(dbProvider, SimpleRepositoryOptions.RunMigrations)).InSingletonScope();
@ -109,7 +109,7 @@ namespace NzbDrone.Core
SetupDefaultQualityProfiles(_kernel.Get<IRepository>()); //Setup the default QualityProfiles on start-up SetupDefaultQualityProfiles(_kernel.Get<IRepository>()); //Setup the default QualityProfiles on start-up
BindIndexers(); BindIndexers();
BindTimers(); BindJobs();
} }
} }
@ -125,10 +125,12 @@ namespace NzbDrone.Core
_kernel.Get<IndexerProvider>().InitializeIndexers(indexers.ToList()); _kernel.Get<IndexerProvider>().InitializeIndexers(indexers.ToList());
} }
private static void BindTimers() private static void BindJobs()
{ {
_kernel.Bind<ITimer>().To<RssSyncTimer>().InTransientScope(); _kernel.Bind<IJob>().To<RssSyncJob>().InTransientScope();
_kernel.Get<TimerProvider>().Initialize(); _kernel.Bind<IJob>().To<NewSeriesUpdate>().InTransientScope();
_kernel.Bind<IJob>().To<UpdateInfoJob>().InTransientScope();
_kernel.Get<JobProvider>().Initialize();
} }
private static void ForceMigration(IRepository repository) private static void ForceMigration(IRepository repository)

@ -169,12 +169,14 @@
<Compile Include="Instrumentation\ExceptioneerTarget.cs" /> <Compile Include="Instrumentation\ExceptioneerTarget.cs" />
<Compile Include="Instrumentation\NlogWriter.cs" /> <Compile Include="Instrumentation\NlogWriter.cs" />
<Compile Include="Providers\Indexer\NzbMatrixProvider.cs" /> <Compile Include="Providers\Indexer\NzbMatrixProvider.cs" />
<Compile Include="Providers\Timers\TimerProvider.cs" /> <Compile Include="Providers\Jobs\NewSeriesUpdate.cs" />
<Compile Include="Providers\Jobs\JobProvider.cs" />
<Compile Include="Providers\Indexer\NewzbinProvider.cs" /> <Compile Include="Providers\Indexer\NewzbinProvider.cs" />
<Compile Include="Providers\Indexer\NzbsRUsProvider.cs" /> <Compile Include="Providers\Indexer\NzbsRUsProvider.cs" />
<Compile Include="Providers\Timers\ITimer.cs" /> <Compile Include="Providers\Jobs\IJob.cs" />
<Compile Include="Providers\Timers\RssSyncTimer.cs" /> <Compile Include="Providers\Jobs\RssSyncJob.cs" />
<Compile Include="Repository\TimerSetting.cs" /> <Compile Include="Providers\Jobs\UpdateInfoJob.cs" />
<Compile Include="Repository\JobSetting.cs" />
<Compile Include="Repository\IndexerSetting.cs" /> <Compile Include="Repository\IndexerSetting.cs" />
<Compile Include="Model\EpisodeParseResult.cs" /> <Compile Include="Model\EpisodeParseResult.cs" />
<Compile Include="Model\EpisodeRenameModel.cs" /> <Compile Include="Model\EpisodeRenameModel.cs" />

@ -129,7 +129,7 @@ namespace NzbDrone.Core.Providers
var newList = new List<Episode>(); var newList = new List<Episode>();
Logger.Debug("Updating season info for series:{0}", targetSeries.SeriesName); Logger.Debug("Updating season info for series:{0}", targetSeries.SeriesName);
targetSeries.Episodes.Select(e => new {e.SeasonId, e.SeasonNumber}) targetSeries.Episodes.Select(e => new { e.SeasonId, e.SeasonNumber })
.Distinct().ToList() .Distinct().ToList()
.ForEach(s => _seasons.EnsureSeason(seriesId, s.SeasonId, s.SeasonNumber)); .ForEach(s => _seasons.EnsureSeason(seriesId, s.SeasonId, s.SeasonNumber));
@ -144,8 +144,7 @@ namespace NzbDrone.Core.Providers
if (episode.FirstAired < new DateTime(1753, 1, 1)) if (episode.FirstAired < new DateTime(1753, 1, 1))
episode.FirstAired = new DateTime(1753, 1, 1); episode.FirstAired = new DateTime(1753, 1, 1);
Logger.Trace("Updating info for series:{0} - episode:{1}", targetSeries.SeriesName, Logger.Trace("Updating info for [{0}] - S{1}E{2}", targetSeries.SeriesName, episode.SeasonNumber, episode.EpisodeNumber);
episode.EpisodeNumber);
var newEpisode = new Episode var newEpisode = new Episode
{ {
AirDate = episode.FirstAired, AirDate = episode.FirstAired,

@ -0,0 +1,28 @@
using NzbDrone.Core.Model.Notification;
namespace NzbDrone.Core.Providers.Jobs
{
public interface IJob
{
/// <summary>
/// Name of the timer.
/// This is the name that will be visible in all UI elements
/// </summary>
string Name { get; }
/// <summary>
/// Default Interval that this job should run at. In seconds.
/// </summary>
int DefaultInterval { get; }
/// <summary>
/// Starts the job
/// </summary>
/// <param name="notification">Notification object that is passed in by JobProvider.
/// this object should be used to update the progress on the UI</param>
/// <param name="targetId">The that should be used to limit the target of this job</param>
void Start(ProgressNotification notification, int targetId);
}
}

@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using NLog;
using NzbDrone.Core.Model.Notification;
using NzbDrone.Core.Repository;
using SubSonic.Repository;
namespace NzbDrone.Core.Providers.Jobs
{
public class JobProvider
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly IRepository _repository;
private readonly NotificationProvider _notificationProvider;
private readonly IEnumerable<IJob> _jobs;
private static readonly object ExecutionLock = new object();
private Thread _jobThread;
private static bool _isRunning;
private ProgressNotification _notification;
public JobProvider(IRepository repository, NotificationProvider notificationProvider, IEnumerable<IJob> jobs)
{
_repository = repository;
_notificationProvider = notificationProvider;
_jobs = jobs;
}
public JobProvider() { }
/// <summary>
/// Returns a list of all registered jobs
/// </summary>
/// <returns></returns>
public virtual List<JobSetting> All()
{
return _repository.All<JobSetting>().ToList();
}
/// <summary>
/// Creates/Updates settings for a job
/// </summary>
/// <param name="settings">Settings to be created/updated</param>
public virtual void SaveSettings(JobSetting settings)
{
if (settings.Id == 0)
{
Logger.Debug("Adding job settings for {0}", settings.Name);
_repository.Add(settings);
}
else
{
Logger.Debug("Updating job settings for {0}", settings.Name);
_repository.Update(settings);
}
}
/// <summary>
/// Iterates through all registered jobs and executed any that are due for an execution.
/// </summary>
/// <returns>True if ran, false if skipped</returns>
public virtual bool RunScheduled()
{
lock (ExecutionLock)
{
if (_isRunning)
{
Logger.Info("Another instance of this job is already running. Ignoring request.");
return false;
}
_isRunning = true;
}
try
{
Logger.Trace("Getting list of jobs needing to be executed");
var pendingJobs = All().Where(
t => t.Enable &&
(DateTime.Now - t.LastExecution) > TimeSpan.FromMinutes(t.Interval)
);
foreach (var pendingTimer in pendingJobs)
{
Logger.Info("Attempting to start job [{0}]. Last executing {1}", pendingTimer.Name,
pendingTimer.LastExecution);
var timerClass = _jobs.Where(t => t.GetType().ToString() == pendingTimer.TypeName).FirstOrDefault();
Execute(timerClass.GetType(), 0);
}
}
finally
{
_isRunning = false;
}
return true;
}
/// <summary>
/// Starts the execution of a job asynchronously
/// </summary>
/// <param name="jobType">Type of the job that should be executed.</param>
/// <param name="targetId">The targetId could be any Id parameter eg. SeriesId. it will be passed to the job implementation
/// to allow it to filter it's target of execution.</param>
/// <returns>True if ran, false if skipped</returns>
public bool BeginExecute(Type jobType, int targetId = 0)
{
lock (ExecutionLock)
{
if (_isRunning)
{
Logger.Info("Another instance of this job is already running. Ignoring request.");
return false;
}
_isRunning = true;
}
Logger.Info("User has requested a manual execution of {0}", jobType.Name);
if (_jobThread == null || !_jobThread.IsAlive)
{
Logger.Debug("Initializing background thread");
ThreadStart starter = () => Execute(jobType, targetId);
_jobThread = new Thread(starter) { Name = "TimerThread", Priority = ThreadPriority.BelowNormal };
_jobThread.Start();
}
else
{
Logger.Warn("Thread still active. Ignoring request.");
}
return true;
}
/// <summary>
/// Executes the job
/// </summary>
/// <param name="jobType">Type of the job that should be executed</param>
/// <param name="targetId">The targetId could be any Id parameter eg. SeriesId. it will be passed to the timer implementation
/// to allow it to filter it's target of execution</param>
private void Execute(Type jobType, int targetId = 0)
{
var timerClass = _jobs.Where(t => t.GetType() == jobType).FirstOrDefault();
if (timerClass == null)
{
Logger.Error("Unable to locate implantation for [{0}]. Make sure its properly registered.", jobType.ToString());
return;
}
try
{
var sw = Stopwatch.StartNew();
using (_notification = new ProgressNotification(timerClass.Name))
{
_notificationProvider.Register(_notification);
timerClass.Start(_notification, targetId);
_notification.Status = ProgressNotificationStatus.Completed;
}
sw.Stop();
Logger.Info("timer [{0}] finished executing successfully. Duration {1}", timerClass.Name, sw.Elapsed.ToString());
}
catch (Exception e)
{
Logger.ErrorException("An error has occurred while executing timer job" + timerClass.Name, e);
}
finally
{
if (_jobThread == Thread.CurrentThread)
{
_isRunning = false;
}
}
}
/// <summary>
/// Initializes jobs in the database using the IJob instances that are
/// registered in CentralDispatch
/// </summary>
public virtual void Initialize()
{
Logger.Info("Initializing jobs. Count {0}", _jobs.Count());
var currentTimer = All();
foreach (var timer in _jobs)
{
var timerProviderLocal = timer;
if (!currentTimer.Exists(c => c.TypeName == timerProviderLocal.GetType().ToString()))
{
var settings = new JobSetting()
{
Enable = true,
TypeName = timer.GetType().ToString(),
Name = timerProviderLocal.Name,
Interval = timerProviderLocal.DefaultInterval,
LastExecution = DateTime.MinValue
};
SaveSettings(settings);
}
}
}
}
}

@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Core.Model.Notification;
namespace NzbDrone.Core.Providers.Jobs
{
public class NewSeriesUpdate : IJob
{
private readonly SeriesProvider _seriesProvider;
private readonly EpisodeProvider _episodeProvider;
private readonly MediaFileProvider _mediaFileProvider;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public NewSeriesUpdate(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, MediaFileProvider mediaFileProvider)
{
_seriesProvider = seriesProvider;
_episodeProvider = episodeProvider;
_mediaFileProvider = mediaFileProvider;
}
public string Name
{
get { return "New Series Update"; }
}
public int DefaultInterval
{
get { return 1; }
}
public void Start(ProgressNotification notification, int targetId)
{
ScanSeries(notification);
}
private void ScanSeries(ProgressNotification notification)
{
var syncList = _seriesProvider.GetAllSeries().Where(s => s.LastInfoSync == null).ToList();
if (syncList.Count == 0) return;
foreach (var currentSeries in syncList)
{
try
{
notification.CurrentStatus = String.Format("Searching For: {0}", new DirectoryInfo(currentSeries.Path).Name);
var updatedSeries = _seriesProvider.UpdateSeriesInfo(currentSeries.SeriesId);
notification.CurrentStatus = String.Format("Downloading episode info For: {0}",
updatedSeries.Title);
_episodeProvider.RefreshEpisodeInfo(updatedSeries.SeriesId);
notification.CurrentStatus = String.Format("Scanning series folder {0}",
updatedSeries.Path);
_mediaFileProvider.Scan(_seriesProvider.GetSeries(updatedSeries.SeriesId));
}
catch (Exception e)
{
Logger.ErrorException(e.Message, e);
}
}
//Keep scanning until there no more shows left.
ScanSeries(notification);
}
}
}

@ -1,15 +1,16 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.Model.Notification;
namespace NzbDrone.Core.Providers.Timers namespace NzbDrone.Core.Providers.Jobs
{ {
public class RssSyncTimer : ITimer public class RssSyncJob : IJob
{ {
private readonly IndexerProvider _indexerProvider; private readonly IndexerProvider _indexerProvider;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public RssSyncTimer(IndexerProvider indexerProvider) public RssSyncJob(IndexerProvider indexerProvider)
{ {
_indexerProvider = indexerProvider; _indexerProvider = indexerProvider;
} }
@ -24,7 +25,7 @@ namespace NzbDrone.Core.Providers.Timers
get { return 15; } get { return 15; }
} }
public void Start() public void Start(ProgressNotification notification, int targetId)
{ {
Logger.Info("Doing Things!!!!"); Logger.Info("Doing Things!!!!");
@ -32,7 +33,7 @@ namespace NzbDrone.Core.Providers.Timers
foreach (var indexerSetting in indexers) foreach (var indexerSetting in indexers)
{ {
} }
} }
} }

@ -0,0 +1,50 @@
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Core.Model.Notification;
using NzbDrone.Core.Repository;
namespace NzbDrone.Core.Providers.Jobs
{
public class UpdateInfoJob : IJob
{
private readonly SeriesProvider _seriesProvider;
private readonly EpisodeProvider _episodeProvider;
public UpdateInfoJob(SeriesProvider seriesProvider, EpisodeProvider episodeProvider)
{
_seriesProvider = seriesProvider;
_episodeProvider = episodeProvider;
}
public string Name
{
get { return "Update Info"; }
}
public int DefaultInterval
{
get { return 1440; } //Daily
}
public void Start(ProgressNotification notification, int targetId)
{
IList<Series> seriesToUpdate;
if (targetId == 0)
{
seriesToUpdate = _seriesProvider.GetAllSeries().ToList();
}
else
{
seriesToUpdate = new List<Series>() { _seriesProvider.GetSeries(targetId) };
}
foreach (var series in seriesToUpdate)
{
notification.CurrentStatus = "Updating series info for " + series.Title;
_seriesProvider.UpdateSeriesInfo(series.SeriesId);
notification.CurrentStatus = "Updating episode info for " + series.Title;
_episodeProvider.RefreshEpisodeInfo(series.SeriesId);
}
}
}
}

@ -147,7 +147,7 @@ namespace NzbDrone.Core.Providers
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Error("An error has occurred while deleting series.", e); Logger.ErrorException("An error has occurred while deleting series.", e);
throw; throw;
} }
} }

@ -13,22 +13,11 @@ namespace NzbDrone.Core.Providers
{ {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly DiskProvider _diskProvider; private readonly DiskProvider _diskProvider;
private readonly EpisodeProvider _episodeProvider;
private readonly MediaFileProvider _mediaFileProvider;
private readonly NotificationProvider _notificationProvider;
private readonly SeriesProvider _seriesProvider; private readonly SeriesProvider _seriesProvider;
private ProgressNotification _seriesSyncNotification; public SyncProvider(SeriesProvider seriesProvider, DiskProvider diskProvider)
private Thread _seriesSyncThread;
public SyncProvider(SeriesProvider seriesProvider, EpisodeProvider episodeProvider,
MediaFileProvider mediaFileProvider, NotificationProvider notificationProvider,
DiskProvider diskProvider)
{ {
_seriesProvider = seriesProvider; _seriesProvider = seriesProvider;
_episodeProvider = episodeProvider;
_mediaFileProvider = mediaFileProvider;
_notificationProvider = notificationProvider;
_diskProvider = diskProvider; _diskProvider = diskProvider;
} }
@ -58,93 +47,5 @@ namespace NzbDrone.Core.Providers
Logger.Debug("{0} unmapped folders detected.", results.Count); Logger.Debug("{0} unmapped folders detected.", results.Count);
return results; return results;
} }
public bool BeginUpdateNewSeries()
{
Logger.Debug("User has requested a scan of new series");
if (_seriesSyncThread == null || !_seriesSyncThread.IsAlive)
{
Logger.Debug("Initializing background scan thread");
_seriesSyncThread = new Thread(SyncNewSeries)
{
Name = "SyncNewSeries",
Priority = ThreadPriority.Lowest
};
_seriesSyncThread.Start();
}
else
{
Logger.Warn("Series folder scan already in progress. Ignoring request.");
//return false if sync was already running, then we can tell the user to try again later
return false;
}
//return true if sync has started
return true;
}
private void SyncNewSeries()
{
Logger.Info("Syncing new series");
try
{
using (_seriesSyncNotification = new ProgressNotification("Series Scan"))
{
_notificationProvider.Register(_seriesSyncNotification);
_seriesSyncNotification.CurrentStatus = "Finding New Series";
ScanSeries();
_seriesSyncNotification.CurrentStatus = "Series Scan Completed";
Logger.Info("Series folders scan has successfully completed.");
Thread.Sleep(3000);
_seriesSyncNotification.Status = ProgressNotificationStatus.Completed;
}
}
catch (Exception e)
{
Logger.ErrorException(e.Message, e);
}
}
private void ScanSeries()
{
var syncList = _seriesProvider.GetAllSeries().Where(s => s.LastInfoSync == null).ToList();
if (syncList.Count == 0) return;
_seriesSyncNotification.ProgressMax = syncList.Count;
foreach (var currentSeries in syncList)
{
try
{
_seriesSyncNotification.CurrentStatus = String.Format("Searching For: {0}", currentSeries.Title);
var updatedSeries = _seriesProvider.UpdateSeriesInfo(currentSeries.SeriesId);
_seriesSyncNotification.CurrentStatus = String.Format("Downloading episode info For: {0}",
updatedSeries.Title);
_episodeProvider.RefreshEpisodeInfo(updatedSeries.SeriesId);
_seriesSyncNotification.CurrentStatus = String.Format("Scanning series folder {0}",
updatedSeries.Path);
_mediaFileProvider.Scan(_seriesProvider.GetSeries(updatedSeries.SeriesId));
//Todo: Launch Backlog search for this series _backlogProvider.StartSearch(mappedSeries.Id);
}
catch (Exception e)
{
Logger.ErrorException(e.Message, e);
}
_seriesSyncNotification.ProgressValue++;
}
//Keep scanning until there no more shows left.
ScanSeries();
}
} }
} }

@ -1,11 +0,0 @@
namespace NzbDrone.Core.Providers.Timers
{
public interface ITimer
{
string Name { get; }
int DefaultInterval { get; }
void Start();
}
}

@ -1,123 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NLog;
using NzbDrone.Core.Repository;
using SubSonic.Repository;
namespace NzbDrone.Core.Providers.Timers
{
public class TimerProvider
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly IRepository _repository;
private readonly IEnumerable<ITimer> _timerJobs;
private static readonly object ExecutionLock = new object();
private static bool _isRunning;
public TimerProvider(IRepository repository, IEnumerable<ITimer> timerJobs)
{
_repository = repository;
_timerJobs = timerJobs;
}
public TimerProvider() { }
public virtual List<TimerSetting> All()
{
return _repository.All<TimerSetting>().ToList();
}
public virtual void SaveSettings(TimerSetting settings)
{
if (settings.Id == 0)
{
Logger.Debug("Adding timer settings for {0}", settings.Name);
_repository.Add(settings);
}
else
{
Logger.Debug("Updating timer settings for {0}", settings.Name);
_repository.Update(settings);
}
}
public virtual void Run()
{
lock (ExecutionLock)
{
if (_isRunning)
{
Logger.Info("Another instance of timer is already running. Ignoring request.");
return;
}
_isRunning = true;
}
Logger.Trace("Getting list of timers needing to be executed");
var pendingTimers = All().Where(
t => t.Enable &&
(DateTime.Now - t.LastExecution) > TimeSpan.FromMinutes(t.Interval)
);
foreach (var pendingTimer in pendingTimers)
{
Logger.Info("Attempting to start timer [{0}]. Last executing {1}", pendingTimer.Name, pendingTimer.LastExecution);
var timerClass = _timerJobs.Where(t => t.GetType().ToString() == pendingTimer.TypeName).FirstOrDefault();
ForceExecute(timerClass.GetType());
}
}
public void ForceExecute(Type timerType)
{
var timerClass = _timerJobs.Where(t => t.GetType() == timerType).FirstOrDefault();
if (timerClass == null)
{
Logger.Error("Unable to locate implantation for [{0}]. Make sure its properly registered.", timerType.ToString());
return;
}
try
{
var sw = Stopwatch.StartNew();
timerClass.Start();
sw.Stop();
Logger.Info("timer [{0}] finished executing successfully. Duration {1}", timerClass.Name, sw.Elapsed.ToString());
}
catch (Exception e)
{
Logger.Error("An error has occurred while executing timer job " + timerClass.Name, e);
}
}
public virtual void Initialize()
{
Logger.Info("Initializing timer jobs. Count {0}", _timerJobs.Count());
var currentTimer = All();
foreach (var timer in _timerJobs)
{
var timerProviderLocal = timer;
if (!currentTimer.Exists(c => c.TypeName == timerProviderLocal.GetType().ToString()))
{
var settings = new TimerSetting()
{
Enable = true,
TypeName = timer.GetType().ToString(),
Name = timerProviderLocal.Name,
Interval = timerProviderLocal.DefaultInterval,
LastExecution = DateTime.MinValue
};
SaveSettings(settings);
}
}
}
}
}

@ -25,29 +25,35 @@ namespace NzbDrone.Core.Providers
public virtual IList<TvdbSearchResult> SearchSeries(string title) public virtual IList<TvdbSearchResult> SearchSeries(string title)
{ {
Logger.Debug("Searching TVDB for '{0}'", title); lock (_handler)
var result = _handler.SearchSeries(title); {
Logger.Debug("Searching TVDB for '{0}'", title);
Logger.Debug("Search for '{0}' returned {1} possible results", title, result.Count); var result = _handler.SearchSeries(title);
return result;
Logger.Debug("Search for '{0}' returned {1} possible results", title, result.Count);
return result;
}
} }
public virtual TvdbSearchResult GetSeries(string title) public virtual TvdbSearchResult GetSeries(string title)
{ {
var searchResults = SearchSeries(title); lock (_handler)
if (searchResults.Count == 0)
return null;
foreach (var tvdbSearchResult in searchResults)
{ {
if (IsTitleMatch(tvdbSearchResult.SeriesName, title)) var searchResults = SearchSeries(title);
if (searchResults.Count == 0)
return null;
foreach (var tvdbSearchResult in searchResults)
{ {
Logger.Debug("Search for '{0}' was successful", title); if (IsTitleMatch(tvdbSearchResult.SeriesName, title))
return tvdbSearchResult; {
Logger.Debug("Search for '{0}' was successful", title);
return tvdbSearchResult;
}
} }
} }
return null; return null;
} }
@ -70,8 +76,11 @@ namespace NzbDrone.Core.Providers
public virtual TvdbSeries GetSeries(int id, bool loadEpisodes) public virtual TvdbSeries GetSeries(int id, bool loadEpisodes)
{ {
Logger.Debug("Fetching SeriesId'{0}' from tvdb", id); lock (_handler)
return _handler.GetSeries(id, TvdbLanguage.DefaultLanguage, loadEpisodes, false, false); {
Logger.Debug("Fetching SeriesId'{0}' from tvdb", id);
return _handler.GetSeries(id, TvdbLanguage.DefaultLanguage, loadEpisodes, false, false);
}
} }
/// <summary> /// <summary>

@ -3,7 +3,7 @@ using SubSonic.SqlGeneration.Schema;
namespace NzbDrone.Core.Repository namespace NzbDrone.Core.Repository
{ {
public class TimerSetting public class JobSetting
{ {
[SubSonicPrimaryKey(true)] [SubSonicPrimaryKey(true)]
public Int32 Id { get; set; } public Int32 Id { get; set; }

@ -4,6 +4,7 @@ using System.IO;
using System.Web.Mvc; using System.Web.Mvc;
using NzbDrone.Core.Providers; using NzbDrone.Core.Providers;
using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Providers.Core;
using NzbDrone.Core.Providers.Jobs;
using NzbDrone.Web.Models; using NzbDrone.Web.Models;
namespace NzbDrone.Web.Controllers namespace NzbDrone.Web.Controllers
@ -14,13 +15,14 @@ namespace NzbDrone.Web.Controllers
private readonly QualityProvider _qualityProvider; private readonly QualityProvider _qualityProvider;
private readonly RootDirProvider _rootFolderProvider; private readonly RootDirProvider _rootFolderProvider;
private readonly SeriesProvider _seriesProvider; private readonly SeriesProvider _seriesProvider;
private readonly JobProvider _jobProvider;
private readonly SyncProvider _syncProvider; private readonly SyncProvider _syncProvider;
private readonly TvDbProvider _tvDbProvider; private readonly TvDbProvider _tvDbProvider;
public AddSeriesController(SyncProvider syncProvider, RootDirProvider rootFolderProvider, public AddSeriesController(SyncProvider syncProvider, RootDirProvider rootFolderProvider,
ConfigProvider configProvider, ConfigProvider configProvider,
QualityProvider qualityProvider, TvDbProvider tvDbProvider, QualityProvider qualityProvider, TvDbProvider tvDbProvider,
SeriesProvider seriesProvider) SeriesProvider seriesProvider, JobProvider jobProvider)
{ {
_syncProvider = syncProvider; _syncProvider = syncProvider;
_rootFolderProvider = rootFolderProvider; _rootFolderProvider = rootFolderProvider;
@ -28,12 +30,13 @@ namespace NzbDrone.Web.Controllers
_qualityProvider = qualityProvider; _qualityProvider = qualityProvider;
_tvDbProvider = tvDbProvider; _tvDbProvider = tvDbProvider;
_seriesProvider = seriesProvider; _seriesProvider = seriesProvider;
_jobProvider = jobProvider;
} }
[HttpPost] [HttpPost]
public JsonResult ScanNewSeries() public JsonResult ScanNewSeries()
{ {
_syncProvider.BeginUpdateNewSeries(); _jobProvider.BeginExecute(typeof(NewSeriesUpdate));
return new JsonResult(); return new JsonResult();
} }
@ -106,7 +109,7 @@ namespace NzbDrone.Web.Controllers
path.Replace('|', Path.DirectorySeparatorChar).Replace('^', Path.VolumeSeparatorChar), seriesId, path.Replace('|', Path.DirectorySeparatorChar).Replace('^', Path.VolumeSeparatorChar), seriesId,
qualityProfileId); qualityProfileId);
ScanNewSeries(); ScanNewSeries();
return new JsonResult {Data = "ok"}; return new JsonResult { Data = "ok" };
} }
[HttpPost] [HttpPost]

@ -4,7 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
using NzbDrone.Core.Providers; using NzbDrone.Core.Providers;
using NzbDrone.Core.Providers.Timers; using NzbDrone.Core.Providers.Jobs;
using NzbDrone.Core.Repository; using NzbDrone.Core.Repository;
using NzbDrone.Web.Models; using NzbDrone.Web.Models;
using Telerik.Web.Mvc; using Telerik.Web.Mvc;
@ -21,27 +21,25 @@ namespace NzbDrone.Web.Controllers
private readonly RenameProvider _renameProvider; private readonly RenameProvider _renameProvider;
private readonly RootDirProvider _rootDirProvider; private readonly RootDirProvider _rootDirProvider;
private readonly SeriesProvider _seriesProvider; private readonly SeriesProvider _seriesProvider;
private readonly SyncProvider _syncProvider;
private readonly TvDbProvider _tvDbProvider; private readonly TvDbProvider _tvDbProvider;
private readonly TimerProvider _timerProvider; private readonly JobProvider _jobProvider;
// //
// GET: /Series/ // GET: /Series/
public SeriesController(SyncProvider syncProvider, SeriesProvider seriesProvider, public SeriesController(SeriesProvider seriesProvider,
EpisodeProvider episodeProvider, EpisodeProvider episodeProvider,
QualityProvider qualityProvider, MediaFileProvider mediaFileProvider, QualityProvider qualityProvider, MediaFileProvider mediaFileProvider,
RenameProvider renameProvider, RootDirProvider rootDirProvider, RenameProvider renameProvider, RootDirProvider rootDirProvider,
TvDbProvider tvDbProvider, TimerProvider timerProvider) TvDbProvider tvDbProvider, JobProvider jobProvider)
{ {
_seriesProvider = seriesProvider; _seriesProvider = seriesProvider;
_episodeProvider = episodeProvider; _episodeProvider = episodeProvider;
_syncProvider = syncProvider;
_qualityProvider = qualityProvider; _qualityProvider = qualityProvider;
_mediaFileProvider = mediaFileProvider; _mediaFileProvider = mediaFileProvider;
_renameProvider = renameProvider; _renameProvider = renameProvider;
_rootDirProvider = rootDirProvider; _rootDirProvider = rootDirProvider;
_tvDbProvider = tvDbProvider; _tvDbProvider = tvDbProvider;
_timerProvider = timerProvider; _jobProvider = jobProvider;
} }
public ActionResult Index() public ActionResult Index()
@ -53,15 +51,10 @@ namespace NzbDrone.Web.Controllers
public ActionResult RssSync() public ActionResult RssSync()
{ {
_timerProvider.ForceExecute(typeof(RssSyncTimer)); _jobProvider.BeginExecute(typeof(RssSyncJob));
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
public ActionResult UnMapped(string path)
{
return View(_syncProvider.GetUnmappedFolders(path).Select(c => new MappingModel { Id = 1, Path = c }).ToList());
}
public ActionResult LoadEpisodes(int seriesId) public ActionResult LoadEpisodes(int seriesId)
{ {
_episodeProvider.RefreshEpisodeInfo(seriesId); _episodeProvider.RefreshEpisodeInfo(seriesId);
@ -108,37 +101,6 @@ namespace NzbDrone.Web.Controllers
}); });
} }
[GridAction]
public ActionResult _AjaxUnmappedFoldersGrid()
{
var unmappedList = new List<AddExistingSeriesModel>();
foreach (var folder in _rootDirProvider.GetAll())
{
foreach (var unmappedFolder in _syncProvider.GetUnmappedFolders(folder.Path))
{
var tvDbSeries = _seriesProvider.MapPathToSeries(unmappedFolder);
//We still want to show this series as unmapped, but we don't know what it will be when mapped
//Todo: Provide the user with a way to manually map a folder to a TvDb series (or make them rename the folder...)
if (tvDbSeries == null)
tvDbSeries = new TvdbSeries { Id = 0, SeriesName = String.Empty };
unmappedList.Add(new AddExistingSeriesModel
{
IsWanted = true,
Path = unmappedFolder,
PathEncoded = Url.Encode(unmappedFolder),
TvDbId = tvDbSeries.Id,
TvDbName = tvDbSeries.SeriesName
});
}
}
return View(new GridModel(unmappedList));
}
public ActionResult SearchForSeries(string seriesName) public ActionResult SearchForSeries(string seriesName)
{ {
var model = new List<SeriesSearchResultModel>(); var model = new List<SeriesSearchResultModel>();
@ -267,6 +229,13 @@ namespace NzbDrone.Web.Controllers
return RedirectToAction("Details", new { seriesId }); return RedirectToAction("Details", new { seriesId });
} }
public ActionResult UpdateInfo(int seriesId)
{
//Syncs the episodes on disk for the specified series
_jobProvider.BeginExecute(typeof(UpdateInfoJob), seriesId);
return RedirectToAction("Details", new { seriesId });
}
public ActionResult RenameAll() public ActionResult RenameAll()
{ {
_renameProvider.RenameAll(); _renameProvider.RenameAll();

@ -1,17 +1,17 @@
using System; using System;
using System.Web.Mvc; using System.Web.Mvc;
using NzbDrone.Core.Providers; using NzbDrone.Core.Providers;
using NzbDrone.Core.Providers.Timers; using NzbDrone.Core.Providers.Jobs;
namespace NzbDrone.Web.Controllers namespace NzbDrone.Web.Controllers
{ {
public class SharedController : Controller public class SharedController : Controller
{ {
private readonly TimerProvider _timerProvider; private readonly JobProvider _jobProvider;
public SharedController(TimerProvider timerProvider) public SharedController(JobProvider jobProvider)
{ {
_timerProvider = timerProvider; _jobProvider = jobProvider;
} }
public ActionResult Index() public ActionResult Index()

@ -3,22 +3,22 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Web; using System.Web;
using System.Web.Mvc; using System.Web.Mvc;
using NzbDrone.Core.Providers.Timers; using NzbDrone.Core.Providers.Jobs;
namespace NzbDrone.Web.Controllers namespace NzbDrone.Web.Controllers
{ {
public class TimersController : Controller public class TimersController : Controller
{ {
private readonly TimerProvider _timerProvider; private readonly JobProvider _jobProvider;
public TimersController(TimerProvider timerProvider) public TimersController(JobProvider jobProvider)
{ {
_timerProvider = timerProvider; _jobProvider = jobProvider;
} }
public ActionResult Index() public ActionResult Index()
{ {
return View(_timerProvider.All()); return View(_jobProvider.All());
} }

@ -19,6 +19,9 @@
items.Add().Text("Scan For Episodes on Disk").Action( items.Add().Text("Scan For Episodes on Disk").Action(
"SyncEpisodesOnDisk", "Series", "SyncEpisodesOnDisk", "Series",
new {seriesId = Model.SeriesId}); new {seriesId = Model.SeriesId});
items.Add().Text("Update Info").Action(
"UpdateInfo", "Series",
new { seriesId = Model.SeriesId });
items.Add().Text("Rename Series").Action("RenameSeries", items.Add().Text("Rename Series").Action("RenameSeries",
"Series", "Series",
new new

@ -1,4 +1,4 @@
@model IEnumerable<NzbDrone.Core.Repository.TimerSetting> @model IEnumerable<NzbDrone.Core.Repository.JobSetting>
@{ @{
Layout = null; Layout = null;
} }

@ -51,7 +51,7 @@ namespace NzbDrone
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Error("An error has occured while trying to update the config file.", e); Logger.ErrorException("An error has occurred while trying to update the config file.", e);
} }

Loading…
Cancel
Save