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>()
{
ResolveType = typeof (T);
ResolveType = typeof(T);
var result = container.Resolve<T>();
SetConstant(result);
ResolveType = null;
return result;
}
@ -61,7 +62,7 @@ namespace AutoMoq
registeredMocks.Add(type, mock);
}
public virtual void SetConstant<T>(T instance) where T : class
public virtual void SetConstant<T>(T instance)
{
container.RegisterInstance(instance);
SetMock(instance.GetType(), null);
@ -125,7 +126,7 @@ namespace AutoMoq
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
@ -142,7 +143,7 @@ namespace AutoMoq
private static Type GetTheMockType<T>() where T : class
{
return typeof (T);
return typeof(T);
}
#endregion

@ -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<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">
<SubType>Code</SubType>
</Compile>
<Compile Include="TimerProviderTest.cs" />
<Compile Include="JobProviderTest.cs" />
<Compile Include="SyncProviderTest.cs" />
<Compile Include="RootDirProviderTest.cs" />
<Compile Include="IndexerProviderTest.cs" />

@ -1,4 +1,5 @@
using System.Threading;
using System;
using System.Threading;
using MbUnit.Framework;
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.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<QualityProvider>().ToSelf().InSingletonScope();
_kernel.Bind<TvDbProvider>().ToSelf().InSingletonScope();
_kernel.Bind<TvDbProvider>().ToSelf().InTransientScope();
_kernel.Bind<HttpProvider>().ToSelf().InSingletonScope();
_kernel.Bind<SeriesProvider>().ToSelf().InSingletonScope();
_kernel.Bind<SeasonProvider>().ToSelf().InSingletonScope();
@ -96,7 +96,7 @@ namespace NzbDrone.Core
_kernel.Bind<NotificationProvider>().ToSelf().InSingletonScope();
_kernel.Bind<LogProvider>().ToSelf().InSingletonScope();
_kernel.Bind<MediaFileProvider>().ToSelf().InSingletonScope();
_kernel.Bind<TimerProvider>().ToSelf().InSingletonScope();
_kernel.Bind<JobProvider>().ToSelf().InSingletonScope();
_kernel.Bind<IndexerProvider>().ToSelf().InSingletonScope();
_kernel.Bind<IRepository>().ToMethod(
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
BindIndexers();
BindTimers();
BindJobs();
}
}
@ -125,10 +125,12 @@ namespace NzbDrone.Core
_kernel.Get<IndexerProvider>().InitializeIndexers(indexers.ToList());
}
private static void BindTimers()
private static void BindJobs()
{
_kernel.Bind<ITimer>().To<RssSyncTimer>().InTransientScope();
_kernel.Get<TimerProvider>().Initialize();
_kernel.Bind<IJob>().To<RssSyncJob>().InTransientScope();
_kernel.Bind<IJob>().To<NewSeriesUpdate>().InTransientScope();
_kernel.Bind<IJob>().To<UpdateInfoJob>().InTransientScope();
_kernel.Get<JobProvider>().Initialize();
}
private static void ForceMigration(IRepository repository)

@ -169,12 +169,14 @@
<Compile Include="Instrumentation\ExceptioneerTarget.cs" />
<Compile Include="Instrumentation\NlogWriter.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\NzbsRUsProvider.cs" />
<Compile Include="Providers\Timers\ITimer.cs" />
<Compile Include="Providers\Timers\RssSyncTimer.cs" />
<Compile Include="Repository\TimerSetting.cs" />
<Compile Include="Providers\Jobs\IJob.cs" />
<Compile Include="Providers\Jobs\RssSyncJob.cs" />
<Compile Include="Providers\Jobs\UpdateInfoJob.cs" />
<Compile Include="Repository\JobSetting.cs" />
<Compile Include="Repository\IndexerSetting.cs" />
<Compile Include="Model\EpisodeParseResult.cs" />
<Compile Include="Model\EpisodeRenameModel.cs" />

@ -129,7 +129,7 @@ namespace NzbDrone.Core.Providers
var newList = new List<Episode>();
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,

@ -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 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)
{
}
}
}

@ -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)
{
Logger.Error("An error has occurred while deleting series.", e);
Logger.ErrorException("An error has occurred while deleting series.", e);
throw;
}
}

@ -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();
}
}
}

@ -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)
{
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);
}
}
/// <summary>

@ -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; }

@ -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]

@ -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<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)
{
var model = new List<SeriesSearchResultModel>();
@ -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();

@ -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()

@ -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());
}

@ -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

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

@ -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);
}

Loading…
Cancel
Save