Remove empty series folders when create empty folders is false

New: Remove empty series folders after disk scan/deleting last episode file and create emoty series folders is disabled

Closes #2395
This commit is contained in:
Mark McDowall 2018-02-10 18:16:59 -08:00
parent 1c36bc5fee
commit a9c54a1f01
6 changed files with 86 additions and 5 deletions

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -27,7 +27,7 @@ namespace NzbDrone.Core.Extras.Files
public abstract class ExtraFileService<TExtraFile> : IExtraFileService<TExtraFile>, public abstract class ExtraFileService<TExtraFile> : IExtraFileService<TExtraFile>,
IHandleAsync<SeriesDeletedEvent>, IHandleAsync<SeriesDeletedEvent>,
IHandleAsync<EpisodeFileDeletedEvent> IHandle<EpisodeFileDeletedEvent>
where TExtraFile : ExtraFile, new() where TExtraFile : ExtraFile, new()
{ {
private readonly IExtraFileRepository<TExtraFile> _repository; private readonly IExtraFileRepository<TExtraFile> _repository;
@ -103,7 +103,7 @@ namespace NzbDrone.Core.Extras.Files
_repository.DeleteForSeries(message.Series.Id); _repository.DeleteForSeries(message.Series.Id);
} }
public void HandleAsync(EpisodeFileDeletedEvent message) public void Handle(EpisodeFileDeletedEvent message)
{ {
var episodeFile = message.EpisodeFile; var episodeFile = message.EpisodeFile;

View File

@ -95,8 +95,10 @@ namespace NzbDrone.Core.MediaFiles
{ {
_logger.Debug("Series folder doesn't exist: {0}", series.Path); _logger.Debug("Series folder doesn't exist: {0}", series.Path);
} }
CleanMediaFiles(series, new List<string>()); CleanMediaFiles(series, new List<string>());
CompletedScanning(series); CompletedScanning(series);
return; return;
} }
@ -113,6 +115,7 @@ namespace NzbDrone.Core.MediaFiles
_logger.Trace("Import decisions complete for: {0} [{1}]", series, decisionsStopwatch.Elapsed); _logger.Trace("Import decisions complete for: {0} [{1}]", series, decisionsStopwatch.Elapsed);
_importApprovedEpisodes.Import(decisions, false); _importApprovedEpisodes.Import(decisions, false);
RemoveEmptySeriesFolder(series.Path);
CompletedScanning(series); CompletedScanning(series);
} }
@ -186,6 +189,15 @@ namespace NzbDrone.Core.MediaFiles
} }
} }
private void RemoveEmptySeriesFolder(string path)
{
if (_diskProvider.GetFiles(path, SearchOption.AllDirectories).Empty() &&
!_configService.CreateEmptySeriesFolders)
{
_diskProvider.DeleteFolder(path, true);
}
}
public void Handle(SeriesUpdatedEvent message) public void Handle(SeriesUpdatedEvent message)
{ {
Scan(message.Series); Scan(message.Series);

View File

@ -4,7 +4,10 @@ using System.Net;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions; using NzbDrone.Core.Exceptions;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Tv.Events;
@ -16,24 +19,29 @@ namespace NzbDrone.Core.MediaFiles
void DeleteEpisodeFile(Series series, EpisodeFile episodeFile); void DeleteEpisodeFile(Series series, EpisodeFile episodeFile);
} }
public class MediaFileDeletionService : IDeleteMediaFiles, IHandleAsync<SeriesDeletedEvent> public class MediaFileDeletionService : IDeleteMediaFiles,
IHandleAsync<SeriesDeletedEvent>,
IHandle<EpisodeFileDeletedEvent>
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IRecycleBinProvider _recycleBinProvider; private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly ISeriesService _seriesService; private readonly ISeriesService _seriesService;
private readonly IConfigService _configService;
private readonly Logger _logger; private readonly Logger _logger;
public MediaFileDeletionService(IDiskProvider diskProvider, public MediaFileDeletionService(IDiskProvider diskProvider,
IRecycleBinProvider recycleBinProvider, IRecycleBinProvider recycleBinProvider,
IMediaFileService mediaFileService, IMediaFileService mediaFileService,
ISeriesService seriesService, ISeriesService seriesService,
IConfigService configService,
Logger logger) Logger logger)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
_recycleBinProvider = recycleBinProvider; _recycleBinProvider = recycleBinProvider;
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_seriesService = seriesService; _seriesService = seriesService;
_configService = configService;
_logger = logger; _logger = logger;
} }
@ -105,5 +113,18 @@ namespace NzbDrone.Core.MediaFiles
} }
} }
} }
[EventHandleOrder(EventHandleOrder.Last)]
public void Handle(EpisodeFileDeletedEvent message)
{
var series = message.EpisodeFile.Series.Value;
var path = series.Path;
if (_diskProvider.GetFiles(path, SearchOption.AllDirectories).Empty() &&
!_configService.CreateEmptySeriesFolders)
{
_diskProvider.DeleteFolder(path, true);
}
}
} }
} }

View File

@ -0,0 +1,22 @@
using System;
namespace NzbDrone.Core.Messaging
{
[AttributeUsage(AttributeTargets.Method)]
public class EventHandleOrderAttribute : Attribute
{
public EventHandleOrder EventHandleOrder { get; set; }
public EventHandleOrderAttribute(EventHandleOrder eventHandleOrder)
{
EventHandleOrder = eventHandleOrder;
}
}
public enum EventHandleOrder
{
First,
Any,
Last
}
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Common; using NzbDrone.Common;
@ -48,7 +49,11 @@ namespace NzbDrone.Core.Messaging.Events
//call synchronous handlers first. //call synchronous handlers first.
foreach (var handler in _serviceFactory.BuildAll<IHandle<TEvent>>()) var handlers = _serviceFactory.BuildAll<IHandle<TEvent>>()
.OrderBy(GetEventHandleOrder)
.ToList();
foreach (var handler in handlers)
{ {
try try
{ {
@ -96,5 +101,25 @@ namespace NzbDrone.Core.Messaging.Events
return string.Format("{0}<{1}>", eventType.Name.Remove(eventType.Name.IndexOf('`')), eventType.GetGenericArguments()[0].Name); return string.Format("{0}<{1}>", eventType.Name.Remove(eventType.Name.IndexOf('`')), eventType.GetGenericArguments()[0].Name);
} }
private int GetEventHandleOrder<TEvent>(IHandle<TEvent> eventHandler) where TEvent : class, IEvent
{
// TODO: Convert "Handle" to nameof(eventHandler.Handle) after .net 4.5
var method = eventHandler.GetType().GetMethod("Handle", new Type[] {typeof(TEvent)});
if (method == null)
{
return (int) EventHandleOrder.Any;
}
var attribute = method.GetCustomAttributes(typeof(EventHandleOrderAttribute), true).FirstOrDefault() as EventHandleOrderAttribute;
if (attribute == null)
{
return (int) EventHandleOrder.Any;
}
return (int)attribute.EventHandleOrder;
}
} }
} }

View File

@ -850,6 +850,7 @@
<Compile Include="Messaging\Events\EventAggregator.cs" /> <Compile Include="Messaging\Events\EventAggregator.cs" />
<Compile Include="Messaging\Events\IEventAggregator.cs" /> <Compile Include="Messaging\Events\IEventAggregator.cs" />
<Compile Include="Messaging\Events\IHandle.cs" /> <Compile Include="Messaging\Events\IHandle.cs" />
<Compile Include="Messaging\EventHandleOrderAttribute.cs" />
<Compile Include="Messaging\IProcessMessage.cs" /> <Compile Include="Messaging\IProcessMessage.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\EpisodeResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\EpisodeResource.cs" />