From 9028e498ca3c1030668ebfd292f10fdd0a485cba Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Wed, 20 Apr 2011 00:44:13 -0700 Subject: [PATCH] Added completely awesome JobProvider. extremely easy to do async/timer tasks with ui status/notification already plugged in. --- NzbDrone.Core.Test/AutoMoq/AutoMoqer.cs | 9 +- NzbDrone.Core.Test/EpisodeProviderTest.cs | 20 ++ NzbDrone.Core.Test/JobProviderTest.cs | 203 +++++++++++++++++ NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 2 +- NzbDrone.Core.Test/ParserTest.cs | 3 +- NzbDrone.Core.Test/TimerProviderTest.cs | 110 --------- NzbDrone.Core/CentralDispatch.cs | 16 +- NzbDrone.Core/NzbDrone.Core.csproj | 10 +- NzbDrone.Core/Providers/EpisodeProvider.cs | 5 +- NzbDrone.Core/Providers/Jobs/IJob.cs | 28 +++ NzbDrone.Core/Providers/Jobs/JobProvider.cs | 214 ++++++++++++++++++ .../Providers/Jobs/NewSeriesUpdate.cs | 71 ++++++ .../RssSyncTimer.cs => Jobs/RssSyncJob.cs} | 11 +- NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs | 50 ++++ NzbDrone.Core/Providers/SeriesProvider.cs | 2 +- NzbDrone.Core/Providers/SyncProvider.cs | 101 +-------- NzbDrone.Core/Providers/Timers/ITimer.cs | 11 - .../Providers/Timers/TimerProvider.cs | 123 ---------- NzbDrone.Core/Providers/TvDbProvider.cs | 39 ++-- .../{TimerSetting.cs => JobSetting.cs} | 2 +- .../Controllers/AddSeriesController.cs | 9 +- NzbDrone.Web/Controllers/SeriesController.cs | 57 ++--- NzbDrone.Web/Controllers/SharedController.cs | 8 +- NzbDrone.Web/Controllers/TimersController.cs | 10 +- NzbDrone.Web/Views/Series/Details.aspx | 3 + NzbDrone.Web/Views/Timers/index.cshtml | 2 +- NzbDrone/IISController.cs | 2 +- 27 files changed, 677 insertions(+), 444 deletions(-) create mode 100644 NzbDrone.Core.Test/JobProviderTest.cs delete mode 100644 NzbDrone.Core.Test/TimerProviderTest.cs create mode 100644 NzbDrone.Core/Providers/Jobs/IJob.cs create mode 100644 NzbDrone.Core/Providers/Jobs/JobProvider.cs create mode 100644 NzbDrone.Core/Providers/Jobs/NewSeriesUpdate.cs rename NzbDrone.Core/Providers/{Timers/RssSyncTimer.cs => Jobs/RssSyncJob.cs} (72%) create mode 100644 NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs delete mode 100644 NzbDrone.Core/Providers/Timers/ITimer.cs delete mode 100644 NzbDrone.Core/Providers/Timers/TimerProvider.cs rename NzbDrone.Core/Repository/{TimerSetting.cs => JobSetting.cs} (93%) diff --git a/NzbDrone.Core.Test/AutoMoq/AutoMoqer.cs b/NzbDrone.Core.Test/AutoMoq/AutoMoqer.cs index 8a00b36cf..b5535fb24 100644 --- a/NzbDrone.Core.Test/AutoMoq/AutoMoqer.cs +++ b/NzbDrone.Core.Test/AutoMoq/AutoMoqer.cs @@ -30,8 +30,9 @@ namespace AutoMoq public virtual T Resolve() { - ResolveType = typeof (T); + ResolveType = typeof(T); var result = container.Resolve(); + SetConstant(result); ResolveType = null; return result; } @@ -61,7 +62,7 @@ namespace AutoMoq registeredMocks.Add(type, mock); } - public virtual void SetConstant(T instance) where T : class + public virtual void SetConstant(T instance) { container.RegisterInstance(instance); SetMock(instance.GetType(), null); @@ -125,7 +126,7 @@ namespace AutoMoq private Mock TheRegisteredMockForThisType(Type type) where T : class { - return (Mock) registeredMocks.Where(x => x.Key == type).First().Value; + return (Mock)registeredMocks.Where(x => x.Key == type).First().Value; } private void CreateANewMockAndRegisterIt(Type type, MockBehavior behavior) where T : class @@ -142,7 +143,7 @@ namespace AutoMoq private static Type GetTheMockType() where T : class { - return typeof (T); + return typeof(T); } #endregion diff --git a/NzbDrone.Core.Test/EpisodeProviderTest.cs b/NzbDrone.Core.Test/EpisodeProviderTest.cs index 78142ebc7..4a340f805 100644 --- a/NzbDrone.Core.Test/EpisodeProviderTest.cs +++ b/NzbDrone.Core.Test/EpisodeProviderTest.cs @@ -87,5 +87,25 @@ namespace NzbDrone.Core.Test //Assert } + + + [Test] + public void Add_daily_show_episodes() + { + var mocker = new AutoMoqer(); + mocker.SetConstant(MockLib.GetEmptyRepository()); + mocker.Resolve(); + const int tvDbSeriesId = 71256; + //act + var seriesProvider = mocker.Resolve(); + + seriesProvider.AddSeries("c:\\test\\", tvDbSeriesId, 0); + var episodeProvider = mocker.Resolve(); + episodeProvider.RefreshEpisodeInfo(tvDbSeriesId); + + //assert + var episodes = episodeProvider.GetEpisodeBySeries(tvDbSeriesId); + Assert.IsNotEmpty(episodes); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/JobProviderTest.cs b/NzbDrone.Core.Test/JobProviderTest.cs new file mode 100644 index 000000000..aa8b86ea3 --- /dev/null +++ b/NzbDrone.Core.Test/JobProviderTest.cs @@ -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 fakeTimers = new List { new FakeJob() }; + var mocker = new AutoMoqer(); + + mocker.SetConstant(MockLib.GetEmptyRepository()); + mocker.SetConstant(fakeTimers); + + var timerProvider = mocker.Resolve(); + 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 fakeTimers = new List { new FakeJob() }; + var mocker = new AutoMoqer(); + + mocker.SetConstant(MockLib.GetEmptyRepository()); + mocker.SetConstant(fakeTimers); + + var timerProvider = mocker.Resolve(); + 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 fakeTimers = new List { new FakeJob() }; + var mocker = new AutoMoqer(); + + mocker.SetConstant(MockLib.GetEmptyRepository()); + mocker.SetConstant(fakeTimers); + + var timerProvider = mocker.Resolve(); + 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 fakeTimers = new List { new SlowJob() }; + var mocker = new AutoMoqer(); + + mocker.SetConstant(MockLib.GetEmptyRepository()); + mocker.SetConstant(fakeTimers); + + var timerProvider = mocker.Resolve(); + 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 fakeTimers = new List { fakeTimer }; + var mocker = new AutoMoqer(); + + mocker.SetConstant(MockLib.GetEmptyRepository()); + mocker.SetConstant(fakeTimers); + + var timerProvider = mocker.Resolve(); + 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 fakeTimers = new List { fakeTimer }; + var mocker = new AutoMoqer(); + + mocker.SetConstant(repo); + mocker.SetConstant(fakeTimers); + + var timerProvider = mocker.Resolve(); + timerProvider.Initialize(); + } + + var mocker2 = new AutoMoqer(); + + mocker2.SetConstant(repo); + var assertTimerProvider = mocker2.Resolve(); + + 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); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 607d586ec..30251ccbb 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -93,7 +93,7 @@ Code - + diff --git a/NzbDrone.Core.Test/ParserTest.cs b/NzbDrone.Core.Test/ParserTest.cs index 312b72b68..2fdf25a7a 100644 --- a/NzbDrone.Core.Test/ParserTest.cs +++ b/NzbDrone.Core.Test/ParserTest.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using MbUnit.Framework; using NzbDrone.Core.Repository.Quality; diff --git a/NzbDrone.Core.Test/TimerProviderTest.cs b/NzbDrone.Core.Test/TimerProviderTest.cs deleted file mode 100644 index 2c399a9cd..000000000 --- a/NzbDrone.Core.Test/TimerProviderTest.cs +++ /dev/null @@ -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 fakeTimers = new List { new FakeTimer() }; - var mocker = new AutoMoqer(); - - mocker.SetConstant(MockLib.GetEmptyRepository()); - mocker.SetConstant(fakeTimers); - - var timerProvider = mocker.Resolve(); - timerProvider.Initialize(); - timerProvider.Run(); - - } - - [Test] - public void Init_Timers() - { - var fakeTimer = new FakeTimer(); - IEnumerable fakeTimers = new List { fakeTimer }; - var mocker = new AutoMoqer(); - - mocker.SetConstant(MockLib.GetEmptyRepository()); - mocker.SetConstant(fakeTimers); - - var timerProvider = mocker.Resolve(); - 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 fakeTimers = new List { fakeTimer }; - var mocker = new AutoMoqer(); - - mocker.SetConstant(repo); - mocker.SetConstant(fakeTimers); - - var timerProvider = mocker.Resolve(); - timerProvider.Initialize(); - } - - var mocker2 = new AutoMoqer(); - - mocker2.SetConstant(repo); - var assertTimerProvider = mocker2.Resolve(); - - 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() - { - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 2b0a057d0..cc96b94f5 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -10,7 +10,7 @@ using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Providers; using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Providers.Indexer; -using NzbDrone.Core.Providers.Timers; +using NzbDrone.Core.Providers.Jobs; using NzbDrone.Core.Repository; using NzbDrone.Core.Repository.Quality; using SubSonic.DataProviders; @@ -77,7 +77,7 @@ namespace NzbDrone.Core dbProvider.Log = new NlogWriter(); _kernel.Bind().ToSelf().InSingletonScope(); - _kernel.Bind().ToSelf().InSingletonScope(); + _kernel.Bind().ToSelf().InTransientScope(); _kernel.Bind().ToSelf().InSingletonScope(); _kernel.Bind().ToSelf().InSingletonScope(); _kernel.Bind().ToSelf().InSingletonScope(); @@ -96,7 +96,7 @@ namespace NzbDrone.Core _kernel.Bind().ToSelf().InSingletonScope(); _kernel.Bind().ToSelf().InSingletonScope(); _kernel.Bind().ToSelf().InSingletonScope(); - _kernel.Bind().ToSelf().InSingletonScope(); + _kernel.Bind().ToSelf().InSingletonScope(); _kernel.Bind().ToSelf().InSingletonScope(); _kernel.Bind().ToMethod( c => new SimpleRepository(dbProvider, SimpleRepositoryOptions.RunMigrations)).InSingletonScope(); @@ -109,7 +109,7 @@ namespace NzbDrone.Core SetupDefaultQualityProfiles(_kernel.Get()); //Setup the default QualityProfiles on start-up BindIndexers(); - BindTimers(); + BindJobs(); } } @@ -125,10 +125,12 @@ namespace NzbDrone.Core _kernel.Get().InitializeIndexers(indexers.ToList()); } - private static void BindTimers() + private static void BindJobs() { - _kernel.Bind().To().InTransientScope(); - _kernel.Get().Initialize(); + _kernel.Bind().To().InTransientScope(); + _kernel.Bind().To().InTransientScope(); + _kernel.Bind().To().InTransientScope(); + _kernel.Get().Initialize(); } private static void ForceMigration(IRepository repository) diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 38bbece87..104a8cf1c 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -169,12 +169,14 @@ - + + - - - + + + + diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index f333854a3..df39da028 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -129,7 +129,7 @@ namespace NzbDrone.Core.Providers var newList = new List(); 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() .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)) episode.FirstAired = new DateTime(1753, 1, 1); - Logger.Trace("Updating info for series:{0} - episode:{1}", targetSeries.SeriesName, - episode.EpisodeNumber); + Logger.Trace("Updating info for [{0}] - S{1}E{2}", targetSeries.SeriesName, episode.SeasonNumber, episode.EpisodeNumber); var newEpisode = new Episode { AirDate = episode.FirstAired, diff --git a/NzbDrone.Core/Providers/Jobs/IJob.cs b/NzbDrone.Core/Providers/Jobs/IJob.cs new file mode 100644 index 000000000..3f7b5e1dc --- /dev/null +++ b/NzbDrone.Core/Providers/Jobs/IJob.cs @@ -0,0 +1,28 @@ +using NzbDrone.Core.Model.Notification; + +namespace NzbDrone.Core.Providers.Jobs +{ + public interface IJob + { + /// + /// Name of the timer. + /// This is the name that will be visible in all UI elements + /// + string Name { get; } + + + /// + /// Default Interval that this job should run at. In seconds. + /// + int DefaultInterval { get; } + + + /// + /// Starts the job + /// + /// Notification object that is passed in by JobProvider. + /// this object should be used to update the progress on the UI + /// The that should be used to limit the target of this job + void Start(ProgressNotification notification, int targetId); + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/JobProvider.cs b/NzbDrone.Core/Providers/Jobs/JobProvider.cs new file mode 100644 index 000000000..df109d372 --- /dev/null +++ b/NzbDrone.Core/Providers/Jobs/JobProvider.cs @@ -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 _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 jobs) + { + _repository = repository; + _notificationProvider = notificationProvider; + _jobs = jobs; + } + + public JobProvider() { } + + + /// + /// Returns a list of all registered jobs + /// + /// + public virtual List All() + { + return _repository.All().ToList(); + } + + /// + /// Creates/Updates settings for a job + /// + /// Settings to be created/updated + 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); + } + } + + /// + /// Iterates through all registered jobs and executed any that are due for an execution. + /// + /// True if ran, false if skipped + 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; + } + + + + /// + /// Starts the execution of a job asynchronously + /// + /// Type of the job that should be executed. + /// 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. + /// True if ran, false if skipped + 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; + } + + /// + /// Executes the job + /// + /// Type of the job that should be executed + /// 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 + 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; + } + } + } + + /// + /// Initializes jobs in the database using the IJob instances that are + /// registered in CentralDispatch + /// + 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); + } + } + } + + + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/NewSeriesUpdate.cs b/NzbDrone.Core/Providers/Jobs/NewSeriesUpdate.cs new file mode 100644 index 000000000..4299c2ffa --- /dev/null +++ b/NzbDrone.Core/Providers/Jobs/NewSeriesUpdate.cs @@ -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); + } + + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Timers/RssSyncTimer.cs b/NzbDrone.Core/Providers/Jobs/RssSyncJob.cs similarity index 72% rename from NzbDrone.Core/Providers/Timers/RssSyncTimer.cs rename to NzbDrone.Core/Providers/Jobs/RssSyncJob.cs index 46305ed15..0dba9d549 100644 --- a/NzbDrone.Core/Providers/Timers/RssSyncTimer.cs +++ b/NzbDrone.Core/Providers/Jobs/RssSyncJob.cs @@ -1,15 +1,16 @@ using System.Linq; 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 static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - public RssSyncTimer(IndexerProvider indexerProvider) + public RssSyncJob(IndexerProvider indexerProvider) { _indexerProvider = indexerProvider; } @@ -24,7 +25,7 @@ namespace NzbDrone.Core.Providers.Timers get { return 15; } } - public void Start() + public void Start(ProgressNotification notification, int targetId) { Logger.Info("Doing Things!!!!"); @@ -32,7 +33,7 @@ namespace NzbDrone.Core.Providers.Timers foreach (var indexerSetting in indexers) { - + } } } diff --git a/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs b/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs new file mode 100644 index 000000000..2485ca8c2 --- /dev/null +++ b/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs @@ -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 seriesToUpdate; + if (targetId == 0) + { + seriesToUpdate = _seriesProvider.GetAllSeries().ToList(); + } + else + { + seriesToUpdate = new List() { _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); + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index 4be836443..ba656c630 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -147,7 +147,7 @@ namespace NzbDrone.Core.Providers } catch (Exception e) { - Logger.Error("An error has occurred while deleting series.", e); + Logger.ErrorException("An error has occurred while deleting series.", e); throw; } } diff --git a/NzbDrone.Core/Providers/SyncProvider.cs b/NzbDrone.Core/Providers/SyncProvider.cs index e649a4dd7..70f353f4f 100644 --- a/NzbDrone.Core/Providers/SyncProvider.cs +++ b/NzbDrone.Core/Providers/SyncProvider.cs @@ -13,22 +13,11 @@ namespace NzbDrone.Core.Providers { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly DiskProvider _diskProvider; - private readonly EpisodeProvider _episodeProvider; - private readonly MediaFileProvider _mediaFileProvider; - private readonly NotificationProvider _notificationProvider; private readonly SeriesProvider _seriesProvider; - private ProgressNotification _seriesSyncNotification; - private Thread _seriesSyncThread; - - public SyncProvider(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, - MediaFileProvider mediaFileProvider, NotificationProvider notificationProvider, - DiskProvider diskProvider) + public SyncProvider(SeriesProvider seriesProvider, DiskProvider diskProvider) { _seriesProvider = seriesProvider; - _episodeProvider = episodeProvider; - _mediaFileProvider = mediaFileProvider; - _notificationProvider = notificationProvider; _diskProvider = diskProvider; } @@ -58,93 +47,5 @@ namespace NzbDrone.Core.Providers Logger.Debug("{0} unmapped folders detected.", results.Count); 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(); - } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Timers/ITimer.cs b/NzbDrone.Core/Providers/Timers/ITimer.cs deleted file mode 100644 index ec4fdd599..000000000 --- a/NzbDrone.Core/Providers/Timers/ITimer.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace NzbDrone.Core.Providers.Timers -{ - public interface ITimer - { - string Name { get; } - - int DefaultInterval { get; } - - void Start(); - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Timers/TimerProvider.cs b/NzbDrone.Core/Providers/Timers/TimerProvider.cs deleted file mode 100644 index 334f0b67f..000000000 --- a/NzbDrone.Core/Providers/Timers/TimerProvider.cs +++ /dev/null @@ -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 _timerJobs; - - private static readonly object ExecutionLock = new object(); - private static bool _isRunning; - - public TimerProvider(IRepository repository, IEnumerable timerJobs) - { - _repository = repository; - _timerJobs = timerJobs; - } - - public TimerProvider() { } - - public virtual List All() - { - return _repository.All().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); - } - } - } - - - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/TvDbProvider.cs b/NzbDrone.Core/Providers/TvDbProvider.cs index 654e68238..ad3340129 100644 --- a/NzbDrone.Core/Providers/TvDbProvider.cs +++ b/NzbDrone.Core/Providers/TvDbProvider.cs @@ -25,29 +25,35 @@ namespace NzbDrone.Core.Providers public virtual IList SearchSeries(string title) { - Logger.Debug("Searching TVDB for '{0}'", title); - var result = _handler.SearchSeries(title); + lock (_handler) + { + Logger.Debug("Searching TVDB for '{0}'", title); - Logger.Debug("Search for '{0}' returned {1} possible results", title, result.Count); - return result; + var result = _handler.SearchSeries(title); + + Logger.Debug("Search for '{0}' returned {1} possible results", title, result.Count); + return result; + } } public virtual TvdbSearchResult GetSeries(string title) { - var searchResults = SearchSeries(title); - if (searchResults.Count == 0) - return null; - - foreach (var tvdbSearchResult in searchResults) + lock (_handler) { - 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); - return tvdbSearchResult; + if (IsTitleMatch(tvdbSearchResult.SeriesName, title)) + { + Logger.Debug("Search for '{0}' was successful", title); + return tvdbSearchResult; + } } } - return null; } @@ -70,8 +76,11 @@ namespace NzbDrone.Core.Providers public virtual TvdbSeries GetSeries(int id, bool loadEpisodes) { - Logger.Debug("Fetching SeriesId'{0}' from tvdb", id); - return _handler.GetSeries(id, TvdbLanguage.DefaultLanguage, loadEpisodes, false, false); + lock (_handler) + { + Logger.Debug("Fetching SeriesId'{0}' from tvdb", id); + return _handler.GetSeries(id, TvdbLanguage.DefaultLanguage, loadEpisodes, false, false); + } } /// diff --git a/NzbDrone.Core/Repository/TimerSetting.cs b/NzbDrone.Core/Repository/JobSetting.cs similarity index 93% rename from NzbDrone.Core/Repository/TimerSetting.cs rename to NzbDrone.Core/Repository/JobSetting.cs index e4a58bf9d..a32ac7d1a 100644 --- a/NzbDrone.Core/Repository/TimerSetting.cs +++ b/NzbDrone.Core/Repository/JobSetting.cs @@ -3,7 +3,7 @@ using SubSonic.SqlGeneration.Schema; namespace NzbDrone.Core.Repository { - public class TimerSetting + public class JobSetting { [SubSonicPrimaryKey(true)] public Int32 Id { get; set; } diff --git a/NzbDrone.Web/Controllers/AddSeriesController.cs b/NzbDrone.Web/Controllers/AddSeriesController.cs index ac2ba8bbc..6e6182309 100644 --- a/NzbDrone.Web/Controllers/AddSeriesController.cs +++ b/NzbDrone.Web/Controllers/AddSeriesController.cs @@ -4,6 +4,7 @@ using System.IO; using System.Web.Mvc; using NzbDrone.Core.Providers; using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Providers.Jobs; using NzbDrone.Web.Models; namespace NzbDrone.Web.Controllers @@ -14,13 +15,14 @@ namespace NzbDrone.Web.Controllers private readonly QualityProvider _qualityProvider; private readonly RootDirProvider _rootFolderProvider; private readonly SeriesProvider _seriesProvider; + private readonly JobProvider _jobProvider; private readonly SyncProvider _syncProvider; private readonly TvDbProvider _tvDbProvider; public AddSeriesController(SyncProvider syncProvider, RootDirProvider rootFolderProvider, ConfigProvider configProvider, QualityProvider qualityProvider, TvDbProvider tvDbProvider, - SeriesProvider seriesProvider) + SeriesProvider seriesProvider, JobProvider jobProvider) { _syncProvider = syncProvider; _rootFolderProvider = rootFolderProvider; @@ -28,12 +30,13 @@ namespace NzbDrone.Web.Controllers _qualityProvider = qualityProvider; _tvDbProvider = tvDbProvider; _seriesProvider = seriesProvider; + _jobProvider = jobProvider; } [HttpPost] public JsonResult ScanNewSeries() { - _syncProvider.BeginUpdateNewSeries(); + _jobProvider.BeginExecute(typeof(NewSeriesUpdate)); return new JsonResult(); } @@ -106,7 +109,7 @@ namespace NzbDrone.Web.Controllers path.Replace('|', Path.DirectorySeparatorChar).Replace('^', Path.VolumeSeparatorChar), seriesId, qualityProfileId); ScanNewSeries(); - return new JsonResult {Data = "ok"}; + return new JsonResult { Data = "ok" }; } [HttpPost] diff --git a/NzbDrone.Web/Controllers/SeriesController.cs b/NzbDrone.Web/Controllers/SeriesController.cs index 468af8972..100320133 100644 --- a/NzbDrone.Web/Controllers/SeriesController.cs +++ b/NzbDrone.Web/Controllers/SeriesController.cs @@ -4,7 +4,7 @@ using System.IO; using System.Linq; using System.Web.Mvc; using NzbDrone.Core.Providers; -using NzbDrone.Core.Providers.Timers; +using NzbDrone.Core.Providers.Jobs; using NzbDrone.Core.Repository; using NzbDrone.Web.Models; using Telerik.Web.Mvc; @@ -21,27 +21,25 @@ namespace NzbDrone.Web.Controllers private readonly RenameProvider _renameProvider; private readonly RootDirProvider _rootDirProvider; private readonly SeriesProvider _seriesProvider; - private readonly SyncProvider _syncProvider; private readonly TvDbProvider _tvDbProvider; - private readonly TimerProvider _timerProvider; + private readonly JobProvider _jobProvider; // // GET: /Series/ - public SeriesController(SyncProvider syncProvider, SeriesProvider seriesProvider, + public SeriesController(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, QualityProvider qualityProvider, MediaFileProvider mediaFileProvider, RenameProvider renameProvider, RootDirProvider rootDirProvider, - TvDbProvider tvDbProvider, TimerProvider timerProvider) + TvDbProvider tvDbProvider, JobProvider jobProvider) { _seriesProvider = seriesProvider; _episodeProvider = episodeProvider; - _syncProvider = syncProvider; _qualityProvider = qualityProvider; _mediaFileProvider = mediaFileProvider; _renameProvider = renameProvider; _rootDirProvider = rootDirProvider; _tvDbProvider = tvDbProvider; - _timerProvider = timerProvider; + _jobProvider = jobProvider; } public ActionResult Index() @@ -53,15 +51,10 @@ namespace NzbDrone.Web.Controllers public ActionResult RssSync() { - _timerProvider.ForceExecute(typeof(RssSyncTimer)); + _jobProvider.BeginExecute(typeof(RssSyncJob)); 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) { _episodeProvider.RefreshEpisodeInfo(seriesId); @@ -108,37 +101,6 @@ namespace NzbDrone.Web.Controllers }); } - [GridAction] - public ActionResult _AjaxUnmappedFoldersGrid() - { - var unmappedList = new List(); - - 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) { var model = new List(); @@ -267,6 +229,13 @@ namespace NzbDrone.Web.Controllers 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() { _renameProvider.RenameAll(); diff --git a/NzbDrone.Web/Controllers/SharedController.cs b/NzbDrone.Web/Controllers/SharedController.cs index 246e6c5a5..ac6fab6bf 100644 --- a/NzbDrone.Web/Controllers/SharedController.cs +++ b/NzbDrone.Web/Controllers/SharedController.cs @@ -1,17 +1,17 @@ using System; using System.Web.Mvc; using NzbDrone.Core.Providers; -using NzbDrone.Core.Providers.Timers; +using NzbDrone.Core.Providers.Jobs; namespace NzbDrone.Web.Controllers { 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() diff --git a/NzbDrone.Web/Controllers/TimersController.cs b/NzbDrone.Web/Controllers/TimersController.cs index 0ad3d8ce0..20e9a3fd1 100644 --- a/NzbDrone.Web/Controllers/TimersController.cs +++ b/NzbDrone.Web/Controllers/TimersController.cs @@ -3,22 +3,22 @@ using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; -using NzbDrone.Core.Providers.Timers; +using NzbDrone.Core.Providers.Jobs; namespace NzbDrone.Web.Controllers { 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() { - return View(_timerProvider.All()); + return View(_jobProvider.All()); } diff --git a/NzbDrone.Web/Views/Series/Details.aspx b/NzbDrone.Web/Views/Series/Details.aspx index e6c41b0ca..0ce7b7c69 100644 --- a/NzbDrone.Web/Views/Series/Details.aspx +++ b/NzbDrone.Web/Views/Series/Details.aspx @@ -19,6 +19,9 @@ items.Add().Text("Scan For Episodes on Disk").Action( "SyncEpisodesOnDisk", "Series", new {seriesId = Model.SeriesId}); + items.Add().Text("Update Info").Action( + "UpdateInfo", "Series", + new { seriesId = Model.SeriesId }); items.Add().Text("Rename Series").Action("RenameSeries", "Series", new diff --git a/NzbDrone.Web/Views/Timers/index.cshtml b/NzbDrone.Web/Views/Timers/index.cshtml index ed84fac0f..d8d7ab783 100644 --- a/NzbDrone.Web/Views/Timers/index.cshtml +++ b/NzbDrone.Web/Views/Timers/index.cshtml @@ -1,4 +1,4 @@ -@model IEnumerable +@model IEnumerable @{ Layout = null; } diff --git a/NzbDrone/IISController.cs b/NzbDrone/IISController.cs index 151b3b3ec..06fa2b6cc 100644 --- a/NzbDrone/IISController.cs +++ b/NzbDrone/IISController.cs @@ -51,7 +51,7 @@ namespace NzbDrone } 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); }