diff --git a/NzbDrone.Common/DiskProvider.cs b/NzbDrone.Common/DiskProvider.cs index a0e10589a..a56e72b1f 100644 --- a/NzbDrone.Common/DiskProvider.cs +++ b/NzbDrone.Common/DiskProvider.cs @@ -225,5 +225,15 @@ namespace NzbDrone.Common return new FileInfo(path).Length; } + + public virtual void FileSetLastWriteTimeUtc(string path, DateTime dateTime) + { + File.SetLastWriteTimeUtc(path, dateTime); + } + + public virtual void DirectorySetLastWriteTimeUtc(string path, DateTime dateTime) + { + Directory.SetLastWriteTimeUtc(path, dateTime); + } } } \ 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 695e3e405..a6efa6cf9 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -123,6 +123,10 @@ + + + + diff --git a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs new file mode 100644 index 000000000..dd4255406 --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs @@ -0,0 +1,107 @@ +// ReSharper disable RedundantUsingDirective + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Model; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.AutoMoq; +using PetaPoco; +using TvdbLib.Data; + +namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class CleanupFixture : CoreTest + { + private const string RecycleBin = @"C:\Test\RecycleBin"; + + private void WithExpired() + { + Mocker.GetMock().Setup(s => s.GetLastDirectoryWrite(It.IsAny())) + .Returns(DateTime.UtcNow.AddDays(-10)); + + Mocker.GetMock().Setup(s => s.GetLastFileWrite(It.IsAny())) + .Returns(DateTime.UtcNow.AddDays(-10)); + } + + private void WithNonExpired() + { + Mocker.GetMock().Setup(s => s.GetLastDirectoryWrite(It.IsAny())) + .Returns(DateTime.UtcNow.AddDays(-3)); + + Mocker.GetMock().Setup(s => s.GetLastFileWrite(It.IsAny())) + .Returns(DateTime.UtcNow.AddDays(-3)); + } + + [SetUp] + public void Setup() + { + Mocker.GetMock().SetupGet(s => s.RecycleBin).Returns(RecycleBin); + + Mocker.GetMock().Setup(s => s.GetDirectories(RecycleBin)) + .Returns(new [] { @"C:\Test\RecycleBin\Folder1", @"C:\Test\RecycleBin\Folder2", @"C:\Test\RecycleBin\Folder3" }); + + Mocker.GetMock().Setup(s => s.GetFiles(RecycleBin, SearchOption.TopDirectoryOnly)) + .Returns(new [] { @"C:\Test\RecycleBin\File1.avi", @"C:\Test\RecycleBin\File2.mkv" }); + } + + [Test] + public void should_return_if_recycleBin_not_configured() + { + Mocker.GetMock().SetupGet(s => s.RecycleBin).Returns(String.Empty); + + Mocker.Resolve().Cleanup(); + + Mocker.GetMock().Verify(v => v.GetDirectories(It.IsAny()), Times.Never()); + } + + [Test] + public void should_delete_all_expired_folders() + { + WithExpired(); + Mocker.Resolve().Cleanup(); + + Mocker.GetMock().Verify(v => v.DeleteFolder(It.IsAny(), true), Times.Exactly(3)); + } + + [Test] + public void should_delete_all_expired_files() + { + WithExpired(); + Mocker.Resolve().Cleanup(); + + Mocker.GetMock().Verify(v => v.DeleteFile(It.IsAny()), Times.Exactly(2)); + } + + [Test] + public void should_not_delete_all_non_expired_folders() + { + WithNonExpired(); + Mocker.Resolve().Cleanup(); + + Mocker.GetMock().Verify(v => v.DeleteFolder(It.IsAny(), true), Times.Never()); + } + + [Test] + public void should_not_delete_all_non_expired_files() + { + WithNonExpired(); + Mocker.Resolve().Cleanup(); + + Mocker.GetMock().Verify(v => v.DeleteFile(It.IsAny()), Times.Never()); + } + } +} diff --git a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs new file mode 100644 index 000000000..e4cd1fd3b --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs @@ -0,0 +1,89 @@ +// ReSharper disable RedundantUsingDirective + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Model; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.AutoMoq; +using PetaPoco; +using TvdbLib.Data; + +namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class DeleteDirectoryFixture : CoreTest + { + private void WithRecycleBin() + { + Mocker.GetMock().SetupGet(s => s.RecycleBin).Returns(@"C:\Test\Recycle Bin"); + } + + private void WithoutRecycleBin() + { + Mocker.GetMock().SetupGet(s => s.RecycleBin).Returns(String.Empty); + } + + [Test] + public void should_use_delete_when_recycleBin_is_not_configured() + { + WithoutRecycleBin(); + + var path = @"C:\Test\TV\30 Rock"; + + Mocker.Resolve().DeleteDirectory(path); + + Mocker.GetMock().Verify(v => v.DeleteFolder(path, true), Times.Once()); + } + + [Test] + public void should_use_move_when_recycleBin_is_configured() + { + WithRecycleBin(); + + var path = @"C:\Test\TV\30 Rock"; + + Mocker.Resolve().DeleteDirectory(path); + + Mocker.GetMock().Verify(v => v.MoveDirectory(path, @"C:\Test\Recycle Bin\30 Rock"), Times.Once()); + } + + [Test] + public void should_call_directorySetLastWriteTime() + { + WithRecycleBin(); + + var path = @"C:\Test\TV\30 Rock"; + + Mocker.Resolve().DeleteDirectory(path); + + Mocker.GetMock().Verify(v => v.DirectorySetLastWriteTimeUtc(@"C:\Test\Recycle Bin\30 Rock", It.IsAny()), Times.Once()); + } + + [Test] + public void should_call_fileSetLastWriteTime_for_each_file() + { + WithRecycleBin(); + var path = @"C:\Test\TV\30 Rock"; + + Mocker.GetMock().Setup(s => s.GetFiles(@"C:\Test\Recycle Bin\30 Rock", SearchOption.AllDirectories)) + .Returns(new[]{ "File1", "File2", "File3" }); + + Mocker.Resolve().DeleteDirectory(path); + + Mocker.GetMock().Verify(v => v.FileSetLastWriteTimeUtc(It.IsAny(), It.IsAny()), Times.Exactly(3)); + } + } +} diff --git a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs new file mode 100644 index 000000000..5ac5c3378 --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs @@ -0,0 +1,75 @@ +// ReSharper disable RedundantUsingDirective + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Model; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.AutoMoq; +using PetaPoco; +using TvdbLib.Data; + +namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class DeleteFileFixture : CoreTest + { + private void WithRecycleBin() + { + Mocker.GetMock().SetupGet(s => s.RecycleBin).Returns(@"C:\Test\Recycle Bin"); + } + + private void WithoutRecycleBin() + { + Mocker.GetMock().SetupGet(s => s.RecycleBin).Returns(String.Empty); + } + + [Test] + public void should_use_delete_when_recycleBin_is_not_configured() + { + WithoutRecycleBin(); + + var path = @"C:\Test\TV\30 Rock\S01E01.avi"; + + Mocker.Resolve().DeleteFile(path); + + Mocker.GetMock().Verify(v => v.DeleteFile(path), Times.Once()); + } + + [Test] + public void should_use_move_when_recycleBin_is_configured() + { + WithRecycleBin(); + + var path = @"C:\Test\TV\30 Rock\S01E01.avi"; + + Mocker.Resolve().DeleteFile(path); + + Mocker.GetMock().Verify(v => v.MoveFile(path, @"C:\Test\Recycle Bin\S01E01.avi"), Times.Once()); + } + + [Test] + public void should_call_fileSetLastWriteTime_for_each_file() + { + WithRecycleBin(); + var path = @"C:\Test\TV\30 Rock\S01E01.avi"; + + + Mocker.Resolve().DeleteFile(path); + + Mocker.GetMock().Verify(v => v.FileSetLastWriteTimeUtc(@"C:\Test\Recycle Bin\S01E01.avi", It.IsAny()), Times.Once()); + } + } +} diff --git a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/EmptyFixture.cs b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/EmptyFixture.cs new file mode 100644 index 000000000..1835dda2a --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/EmptyFixture.cs @@ -0,0 +1,69 @@ +// ReSharper disable RedundantUsingDirective + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Model; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.AutoMoq; +using PetaPoco; +using TvdbLib.Data; + +namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class EmptyFixture : CoreTest + { + private const string RecycleBin = @"C:\Test\RecycleBin"; + + [SetUp] + public void Setup() + { + Mocker.GetMock().SetupGet(s => s.RecycleBin).Returns(RecycleBin); + + Mocker.GetMock().Setup(s => s.GetDirectories(RecycleBin)) + .Returns(new [] { @"C:\Test\RecycleBin\Folder1", @"C:\Test\RecycleBin\Folder2", @"C:\Test\RecycleBin\Folder3" }); + + Mocker.GetMock().Setup(s => s.GetFiles(RecycleBin, SearchOption.TopDirectoryOnly)) + .Returns(new [] { @"C:\Test\RecycleBin\File1.avi", @"C:\Test\RecycleBin\File2.mkv" }); + } + + [Test] + public void should_return_if_recycleBin_not_configured() + { + Mocker.GetMock().SetupGet(s => s.RecycleBin).Returns(String.Empty); + + Mocker.Resolve().Empty(); + + Mocker.GetMock().Verify(v => v.GetDirectories(It.IsAny()), Times.Never()); + } + + [Test] + public void should_delete_all_folders() + { + Mocker.Resolve().Empty(); + + Mocker.GetMock().Verify(v => v.DeleteFolder(It.IsAny(), true), Times.Exactly(3)); + } + + [Test] + public void should_delete_all_files() + { + Mocker.Resolve().Empty(); + + Mocker.GetMock().Verify(v => v.DeleteFile(It.IsAny()), Times.Exactly(2)); + } + } +} diff --git a/NzbDrone.Core/Jobs/CleanupRecycleBinJob.cs b/NzbDrone.Core/Jobs/CleanupRecycleBinJob.cs new file mode 100644 index 000000000..f8c62cdb3 --- /dev/null +++ b/NzbDrone.Core/Jobs/CleanupRecycleBinJob.cs @@ -0,0 +1,36 @@ +using System.Linq; +using System; +using Ninject; +using NLog; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Converting; + +namespace NzbDrone.Core.Jobs +{ + public class CleanupRecycleBinJob : IJob + { + private readonly RecycleBinProvider _recycleBinProvider; + + [Inject] + public CleanupRecycleBinJob(RecycleBinProvider recycleBinProvider) + { + _recycleBinProvider = recycleBinProvider; + } + + public string Name + { + get { return "Cleanup Recycle Bin"; } + } + + public TimeSpan DefaultInterval + { + get { return TimeSpan.FromDays(24); } + } + + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) + { + _recycleBinProvider.Cleanup(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Jobs/DeleteSeriesJob.cs b/NzbDrone.Core/Jobs/DeleteSeriesJob.cs index f3555fbc3..ac910e146 100644 --- a/NzbDrone.Core/Jobs/DeleteSeriesJob.cs +++ b/NzbDrone.Core/Jobs/DeleteSeriesJob.cs @@ -11,15 +11,15 @@ namespace NzbDrone.Core.Jobs public class DeleteSeriesJob : IJob { private readonly SeriesProvider _seriesProvider; - private readonly DiskProvider _diskProvider; + private readonly RecycleBinProvider _recycleBinProvider; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); [Inject] - public DeleteSeriesJob(SeriesProvider seriesProvider, DiskProvider diskProvider) + public DeleteSeriesJob(SeriesProvider seriesProvider, RecycleBinProvider recycleBinProvider) { _seriesProvider = seriesProvider; - _diskProvider = diskProvider; + _recycleBinProvider = recycleBinProvider; } public string Name @@ -54,7 +54,7 @@ namespace NzbDrone.Core.Jobs { notification.CurrentMessage = String.Format("Deleting files from disk for series '{0}'", title); - _diskProvider.DeleteFolder(series.Path, true); + _recycleBinProvider.DeleteDirectory(series.Path); notification.CurrentMessage = String.Format("Successfully deleted files from disk for series '{0}'", title); } diff --git a/NzbDrone.Core/Jobs/EmptyRecycleBinJob.cs b/NzbDrone.Core/Jobs/EmptyRecycleBinJob.cs new file mode 100644 index 000000000..46d390492 --- /dev/null +++ b/NzbDrone.Core/Jobs/EmptyRecycleBinJob.cs @@ -0,0 +1,36 @@ +using System.Linq; +using System; +using Ninject; +using NLog; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Converting; + +namespace NzbDrone.Core.Jobs +{ + public class EmptyRecycleBinJob : IJob + { + private readonly RecycleBinProvider _recycleBinProvider; + + [Inject] + public EmptyRecycleBinJob(RecycleBinProvider recycleBinProvider) + { + _recycleBinProvider = recycleBinProvider; + } + + public string Name + { + get { return "Empty Recycle Bin"; } + } + + public TimeSpan DefaultInterval + { + get { return TimeSpan.FromTicks(0); } + } + + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) + { + _recycleBinProvider.Empty(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 2bdf59ae4..abfe30b93 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -253,6 +253,8 @@ + + @@ -297,6 +299,7 @@ + diff --git a/NzbDrone.Core/Providers/Core/ConfigProvider.cs b/NzbDrone.Core/Providers/Core/ConfigProvider.cs index 06e4770f5..0a371981d 100644 --- a/NzbDrone.Core/Providers/Core/ConfigProvider.cs +++ b/NzbDrone.Core/Providers/Core/ConfigProvider.cs @@ -526,6 +526,12 @@ namespace NzbDrone.Core.Providers.Core set { SetValue("PneumaticDirectory", value); } } + public virtual string RecycleBin + { + get { return GetValue("RecycleBin", String.Empty); } + set { SetValue("RecycleBin", value); } + } + private string GetValue(string key) { return GetValue(key, String.Empty); diff --git a/NzbDrone.Core/Providers/DiskScanProvider.cs b/NzbDrone.Core/Providers/DiskScanProvider.cs index 215b17f63..bf9e2db24 100644 --- a/NzbDrone.Core/Providers/DiskScanProvider.cs +++ b/NzbDrone.Core/Providers/DiskScanProvider.cs @@ -23,12 +23,14 @@ namespace NzbDrone.Core.Providers private readonly DownloadProvider _downloadProvider; private readonly SignalRProvider _signalRProvider; private readonly ConfigProvider _configProvider; + private readonly RecycleBinProvider _recycleBinProvider; [Inject] public DiskScanProvider(DiskProvider diskProvider, EpisodeProvider episodeProvider, SeriesProvider seriesProvider, MediaFileProvider mediaFileProvider, ExternalNotificationProvider externalNotificationProvider, DownloadProvider downloadProvider, - SignalRProvider signalRProvider, ConfigProvider configProvider) + SignalRProvider signalRProvider, ConfigProvider configProvider, + RecycleBinProvider recycleBinProvider) { _diskProvider = diskProvider; _episodeProvider = episodeProvider; @@ -38,6 +40,7 @@ namespace NzbDrone.Core.Providers _downloadProvider = downloadProvider; _signalRProvider = signalRProvider; _configProvider = configProvider; + _recycleBinProvider = recycleBinProvider; } public DiskScanProvider() @@ -135,7 +138,7 @@ namespace NzbDrone.Core.Providers { Logger.Debug("Deleting the existing file(s) on disk to upgrade to: {0}", filePath); //Do the delete for files where there is already an episode on disk - episodes.Where(e => e.EpisodeFile != null).Select(e => e.EpisodeFile.Path).Distinct().ToList().ForEach(p => _diskProvider.DeleteFile(p)); + episodes.Where(e => e.EpisodeFile != null).Select(e => e.EpisodeFile.Path).Distinct().ToList().ForEach(p => _recycleBinProvider.DeleteFile(p)); } else diff --git a/NzbDrone.Core/Providers/RecycleBinProvider.cs b/NzbDrone.Core/Providers/RecycleBinProvider.cs new file mode 100644 index 000000000..698f38619 --- /dev/null +++ b/NzbDrone.Core/Providers/RecycleBinProvider.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using NLog; +using Ninject; +using NzbDrone.Common; +using NzbDrone.Core.Providers.Core; + +namespace NzbDrone.Core.Providers +{ + public class RecycleBinProvider + { + private readonly DiskProvider _diskProvider; + private readonly ConfigProvider _configProvider; + + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + [Inject] + public RecycleBinProvider(DiskProvider diskProvider, ConfigProvider configProvider) + { + _diskProvider = diskProvider; + _configProvider = configProvider; + } + + public RecycleBinProvider() + { + } + + public virtual void DeleteDirectory(string path) + { + logger.Trace("Attempting to send '{0}' to recycling bin", path); + var recyclingBin = _configProvider.RecycleBin; + + if (String.IsNullOrWhiteSpace(recyclingBin)) + { + logger.Info("Recycling Bin has not been configured, deleting permanently."); + _diskProvider.DeleteFolder(path, true); + logger.Trace("Folder has been permanently deleted: {0}", path); + } + + else + { + var destination = Path.Combine(recyclingBin, new DirectoryInfo(path).Name); + + logger.Trace("Moving '{0}' to '{1}'", path, destination); + _diskProvider.MoveDirectory(path, destination); + + logger.Trace("Setting last accessed: {0}", path); + _diskProvider.DirectorySetLastWriteTimeUtc(destination, DateTime.UtcNow); + foreach(var file in _diskProvider.GetFiles(destination, SearchOption.AllDirectories)) + { + _diskProvider.FileSetLastWriteTimeUtc(file, DateTime.UtcNow); + } + + logger.Trace("Folder has been moved to the recycling bin: {0}", destination); + } + } + + public virtual void DeleteFile(string path) + { + logger.Trace("Attempting to send '{0}' to recycling bin", path); + var recyclingBin = _configProvider.RecycleBin; + + if (String.IsNullOrWhiteSpace(recyclingBin)) + { + logger.Info("Recycling Bin has not been configured, deleting permanently."); + _diskProvider.DeleteFile(path); + logger.Trace("File has been permanently deleted: {0}", path); + } + + else + { + var destination = Path.Combine(recyclingBin, new FileInfo(path).Name); + + logger.Trace("Moving '{0}' to '{1}'", path, destination); + _diskProvider.MoveFile(path, destination); + _diskProvider.FileSetLastWriteTimeUtc(destination, DateTime.UtcNow); + logger.Trace("File has been moved to the recycling bin: {0}", destination); + } + } + + public virtual void Empty() + { + if (String.IsNullOrWhiteSpace(_configProvider.RecycleBin)) + { + logger.Info("Recycle Bin has not been configured, cannot empty."); + return; + } + + logger.Info("Removing all items from the recycling bin"); + + foreach (var folder in _diskProvider.GetDirectories(_configProvider.RecycleBin)) + { + _diskProvider.DeleteFolder(folder, true); + } + + foreach (var file in _diskProvider.GetFiles(_configProvider.RecycleBin, SearchOption.TopDirectoryOnly)) + { + _diskProvider.DeleteFile(file); + } + + logger.Trace("Recycling Bin has been emptied."); + } + + public virtual void Cleanup() + { + if (String.IsNullOrWhiteSpace(_configProvider.RecycleBin)) + { + logger.Info("Recycle Bin has not been configured, cannot cleanup."); + return; + } + + logger.Info("Removing items older than 7 days from the recycling bin"); + + foreach (var folder in _diskProvider.GetDirectories(_configProvider.RecycleBin)) + { + if (_diskProvider.GetLastDirectoryWrite(folder).AddDays(7) > DateTime.UtcNow) + { + logger.Trace("Folder hasn't expired yet, skipping: {0}", folder); + continue; + } + + _diskProvider.DeleteFolder(folder, true); + } + + foreach (var file in _diskProvider.GetFiles(_configProvider.RecycleBin, SearchOption.TopDirectoryOnly)) + { + if (_diskProvider.GetLastFileWrite(file).AddDays(7) > DateTime.UtcNow) + { + logger.Trace("File hasn't expired yet, skipping: {0}", file); + continue; + } + + _diskProvider.DeleteFile(file); + } + + logger.Trace("Recycling Bin has been cleaned up."); + } + } +}