diff --git a/src/NzbDrone.Common.Test/TPLTests/DebouncerFixture.cs b/src/NzbDrone.Common.Test/TPLTests/DebouncerFixture.cs index 918409fa4..960da5fd6 100644 --- a/src/NzbDrone.Common.Test/TPLTests/DebouncerFixture.cs +++ b/src/NzbDrone.Common.Test/TPLTests/DebouncerFixture.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Common.Test.TPLTests } [Test] - public void should_throttle_cals() + public void should_throttle_calls() { var counter = new Counter(); var debounceFunction = new Debouncer(counter.Hit, TimeSpan.FromMilliseconds(50)); @@ -64,6 +64,62 @@ namespace NzbDrone.Common.Test.TPLTests } + [Test] + public void should_hold_the_call_while_paused() + { + var counter = new Counter(); + var debounceFunction = new Debouncer(counter.Hit, TimeSpan.FromMilliseconds(50)); + debounceFunction.Pause(); + + debounceFunction.Execute(); + debounceFunction.Execute(); + + Thread.Sleep(100); + + counter.Count.Should().Be(0); + + debounceFunction.Execute(); + debounceFunction.Execute(); + + Thread.Sleep(100); + + counter.Count.Should().Be(0); + + debounceFunction.Resume(); + + Thread.Sleep(20); + + counter.Count.Should().Be(0); + + Thread.Sleep(100); + + counter.Count.Should().Be(1); + } + + [Test] + public void should_handle_pause_reentrancy() + { + var counter = new Counter(); + var debounceFunction = new Debouncer(counter.Hit, TimeSpan.FromMilliseconds(50)); + + debounceFunction.Pause(); + debounceFunction.Pause(); + + debounceFunction.Execute(); + debounceFunction.Execute(); + + debounceFunction.Resume(); + + Thread.Sleep(100); + + counter.Count.Should().Be(0); + + debounceFunction.Resume(); + + Thread.Sleep(100); + + counter.Count.Should().Be(1); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Common/TPL/Debouncer.cs b/src/NzbDrone.Common/TPL/Debouncer.cs index 328d79aa8..51b93c777 100644 --- a/src/NzbDrone.Common/TPL/Debouncer.cs +++ b/src/NzbDrone.Common/TPL/Debouncer.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; namespace NzbDrone.Common.TPL { @@ -7,6 +8,9 @@ namespace NzbDrone.Common.TPL private readonly Action _action; private readonly System.Timers.Timer _timer; + private volatile int _paused; + private volatile bool _triggered; + public Debouncer(Action action, TimeSpan debounceDuration) { _action = action; @@ -16,13 +20,45 @@ namespace NzbDrone.Common.TPL void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { - _timer.Stop(); - _action(); + if (_paused == 0) + { + _triggered = false; + _timer.Stop(); + _action(); + } } public void Execute() { - _timer.Start(); + lock (_timer) + { + _triggered = true; + if (_paused == 0) + { + _timer.Start(); + } + } + } + + public void Pause() + { + lock (_timer) + { + _paused++; + _timer.Stop(); + } + } + + public void Resume() + { + lock (_timer) + { + _paused--; + if (_paused == 0 && _triggered) + { + _timer.Start(); + } + } } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs index f359c1f3b..22156255c 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs @@ -12,11 +12,12 @@ using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Download.TrackedDownloads { public class DownloadMonitoringService : IExecute, - IHandleAsync, - IHandleAsync + IHandle, + IHandle { private readonly IProvideDownloadClient _downloadClientProvider; private readonly IEventAggregator _eventAggregator; + private readonly IManageCommandQueue _manageCommandQueue; private readonly IConfigService _configService; private readonly IFailedDownloadService _failedDownloadService; private readonly ICompletedDownloadService _completedDownloadService; @@ -26,6 +27,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads public DownloadMonitoringService(IProvideDownloadClient downloadClientProvider, IEventAggregator eventAggregator, + IManageCommandQueue manageCommandQueue, IConfigService configService, IFailedDownloadService failedDownloadService, ICompletedDownloadService completedDownloadService, @@ -34,28 +36,42 @@ namespace NzbDrone.Core.Download.TrackedDownloads { _downloadClientProvider = downloadClientProvider; _eventAggregator = eventAggregator; + _manageCommandQueue = manageCommandQueue; _configService = configService; _failedDownloadService = failedDownloadService; _completedDownloadService = completedDownloadService; _trackedDownloadService = trackedDownloadService; _logger = logger; - _refreshDebounce = new Debouncer(Refresh, TimeSpan.FromSeconds(5)); + _refreshDebounce = new Debouncer(QueueRefresh, TimeSpan.FromSeconds(5)); + } + + private void QueueRefresh() + { + _manageCommandQueue.Push(new CheckForFinishedDownloadCommand()); } private void Refresh() { - var downloadClients = _downloadClientProvider.GetDownloadClients(); - - var trackedDownload = new List(); - - foreach (var downloadClient in downloadClients) + _refreshDebounce.Pause(); + try { - var clientTrackedDownloads = ProcessClientDownloads(downloadClient); - trackedDownload.AddRange(clientTrackedDownloads.Where(c => c.State == TrackedDownloadStage.Downloading)); - } + var downloadClients = _downloadClientProvider.GetDownloadClients(); - _eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(trackedDownload)); + var trackedDownload = new List(); + + foreach (var downloadClient in downloadClients) + { + var clientTrackedDownloads = ProcessClientDownloads(downloadClient); + trackedDownload.AddRange(clientTrackedDownloads.Where(c => c.State == TrackedDownloadStage.Downloading)); + } + + _eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(trackedDownload)); + } + finally + { + _refreshDebounce.Resume(); + } } private List ProcessClientDownloads(IDownloadClient downloadClient) @@ -128,12 +144,12 @@ namespace NzbDrone.Core.Download.TrackedDownloads Refresh(); } - public void HandleAsync(EpisodeGrabbedEvent message) + public void Handle(EpisodeGrabbedEvent message) { _refreshDebounce.Execute(); } - public void HandleAsync(EpisodeImportedEvent message) + public void Handle(EpisodeImportedEvent message) { _refreshDebounce.Execute(); }