|
|
|
@ -7,159 +7,179 @@ using NLog;
|
|
|
|
|
using NzbDrone.Common.Disk;
|
|
|
|
|
using NzbDrone.Common.Extensions;
|
|
|
|
|
using NzbDrone.Common.Http;
|
|
|
|
|
using NzbDrone.Core.Datastore;
|
|
|
|
|
using NzbDrone.Core.MediaCover;
|
|
|
|
|
using NzbDrone.Core.Configuration;
|
|
|
|
|
using NzbDrone.Core.Extras.Files;
|
|
|
|
|
using NzbDrone.Core.Extras.Metadata.Files;
|
|
|
|
|
using NzbDrone.Core.MediaFiles;
|
|
|
|
|
using NzbDrone.Core.MediaFiles.Events;
|
|
|
|
|
using NzbDrone.Core.Messaging.Events;
|
|
|
|
|
using NzbDrone.Core.Metadata.Files;
|
|
|
|
|
using NzbDrone.Core.Tv;
|
|
|
|
|
|
|
|
|
|
namespace NzbDrone.Core.Metadata
|
|
|
|
|
namespace NzbDrone.Core.Extras.Metadata
|
|
|
|
|
{
|
|
|
|
|
public class MetadataService : IHandle<MediaCoversUpdatedEvent>,
|
|
|
|
|
IHandle<EpisodeImportedEvent>,
|
|
|
|
|
IHandle<EpisodeFolderCreatedEvent>,
|
|
|
|
|
IHandle<SeriesRenamedEvent>
|
|
|
|
|
public class MetadataService : ExtraFileManager<MetadataFile>
|
|
|
|
|
{
|
|
|
|
|
private readonly IMetadataFactory _metadataFactory;
|
|
|
|
|
private readonly IMetadataFileService _metadataFileService;
|
|
|
|
|
private readonly ICleanMetadataService _cleanMetadataService;
|
|
|
|
|
private readonly IMediaFileService _mediaFileService;
|
|
|
|
|
private readonly IEpisodeService _episodeService;
|
|
|
|
|
private readonly IDiskTransferService _diskTransferService;
|
|
|
|
|
private readonly IDiskProvider _diskProvider;
|
|
|
|
|
private readonly IHttpClient _httpClient;
|
|
|
|
|
private readonly IMediaFileAttributeService _mediaFileAttributeService;
|
|
|
|
|
private readonly IEventAggregator _eventAggregator;
|
|
|
|
|
private readonly IMetadataFileService _metadataFileService;
|
|
|
|
|
private readonly Logger _logger;
|
|
|
|
|
|
|
|
|
|
public MetadataService(IMetadataFactory metadataFactory,
|
|
|
|
|
IMetadataFileService metadataFileService,
|
|
|
|
|
ICleanMetadataService cleanMetadataService,
|
|
|
|
|
IMediaFileService mediaFileService,
|
|
|
|
|
IEpisodeService episodeService,
|
|
|
|
|
public MetadataService(IConfigService configService,
|
|
|
|
|
IDiskTransferService diskTransferService,
|
|
|
|
|
IMetadataFactory metadataFactory,
|
|
|
|
|
ICleanMetadataService cleanMetadataService,
|
|
|
|
|
IDiskProvider diskProvider,
|
|
|
|
|
IHttpClient httpClient,
|
|
|
|
|
IMediaFileAttributeService mediaFileAttributeService,
|
|
|
|
|
IEventAggregator eventAggregator,
|
|
|
|
|
IMetadataFileService metadataFileService,
|
|
|
|
|
Logger logger)
|
|
|
|
|
: base(configService, diskTransferService, metadataFileService)
|
|
|
|
|
{
|
|
|
|
|
_metadataFactory = metadataFactory;
|
|
|
|
|
_metadataFileService = metadataFileService;
|
|
|
|
|
_cleanMetadataService = cleanMetadataService;
|
|
|
|
|
_mediaFileService = mediaFileService;
|
|
|
|
|
_episodeService = episodeService;
|
|
|
|
|
_diskTransferService = diskTransferService;
|
|
|
|
|
_diskProvider = diskProvider;
|
|
|
|
|
_httpClient = httpClient;
|
|
|
|
|
_mediaFileAttributeService = mediaFileAttributeService;
|
|
|
|
|
_eventAggregator = eventAggregator;
|
|
|
|
|
_metadataFileService = metadataFileService;
|
|
|
|
|
_logger = logger;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Handle(MediaCoversUpdatedEvent message)
|
|
|
|
|
public override int Order
|
|
|
|
|
{
|
|
|
|
|
_cleanMetadataService.Clean(message.Series);
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles)
|
|
|
|
|
{
|
|
|
|
|
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
|
|
|
|
|
_cleanMetadataService.Clean(series);
|
|
|
|
|
|
|
|
|
|
if (!_diskProvider.FolderExists(message.Series.Path))
|
|
|
|
|
if (!_diskProvider.FolderExists(series.Path))
|
|
|
|
|
{
|
|
|
|
|
_logger.Info("Series folder does not exist, skipping metadata creation");
|
|
|
|
|
return;
|
|
|
|
|
return Enumerable.Empty<MetadataFile>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var seriesMetadataFiles = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
|
|
|
|
var episodeFiles = GetEpisodeFiles(message.Series.Id);
|
|
|
|
|
var files = new List<MetadataFile>();
|
|
|
|
|
|
|
|
|
|
foreach (var consumer in _metadataFactory.Enabled())
|
|
|
|
|
{
|
|
|
|
|
var consumerFiles = GetMetadataFilesForConsumer(consumer, seriesMetadataFiles);
|
|
|
|
|
var files = new List<MetadataFile>();
|
|
|
|
|
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
|
|
|
|
|
|
|
|
|
|
files.AddIfNotNull(ProcessSeriesMetadata(consumer, message.Series, consumerFiles));
|
|
|
|
|
files.AddRange(ProcessSeriesImages(consumer, message.Series, consumerFiles));
|
|
|
|
|
files.AddRange(ProcessSeasonImages(consumer, message.Series, consumerFiles));
|
|
|
|
|
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
|
|
|
|
|
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
|
|
|
|
|
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
|
|
|
|
|
|
|
|
|
|
foreach (var episodeFile in episodeFiles)
|
|
|
|
|
{
|
|
|
|
|
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, message.Series, episodeFile, consumerFiles));
|
|
|
|
|
files.AddRange(ProcessEpisodeImages(consumer, message.Series, episodeFile, consumerFiles));
|
|
|
|
|
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, consumerFiles));
|
|
|
|
|
files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, consumerFiles));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_metadataFileService.Upsert(files);
|
|
|
|
|
|
|
|
|
|
return files;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Handle(EpisodeImportedEvent message)
|
|
|
|
|
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
|
|
|
|
|
{
|
|
|
|
|
var files = new List<MetadataFile>();
|
|
|
|
|
|
|
|
|
|
foreach (var consumer in _metadataFactory.Enabled())
|
|
|
|
|
{
|
|
|
|
|
var files = new List<MetadataFile>();
|
|
|
|
|
|
|
|
|
|
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, message.EpisodeInfo.Series, message.ImportedEpisode, new List<MetadataFile>()));
|
|
|
|
|
files.AddRange(ProcessEpisodeImages(consumer, message.EpisodeInfo.Series, message.ImportedEpisode, new List<MetadataFile>()));
|
|
|
|
|
|
|
|
|
|
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
|
|
|
|
|
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, new List<MetadataFile>()));
|
|
|
|
|
files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, new List<MetadataFile>()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_metadataFileService.Upsert(files);
|
|
|
|
|
|
|
|
|
|
return files;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Handle(EpisodeFolderCreatedEvent message)
|
|
|
|
|
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder)
|
|
|
|
|
{
|
|
|
|
|
if (message.SeriesFolder.IsNullOrWhiteSpace() && message.SeasonFolder.IsNullOrWhiteSpace())
|
|
|
|
|
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
|
|
|
|
|
|
|
|
|
|
if (seriesFolder.IsNullOrWhiteSpace() && seasonFolder.IsNullOrWhiteSpace())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
return new List<MetadataFile>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var seriesMetadataFiles = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
|
|
|
|
var files = new List<MetadataFile>();
|
|
|
|
|
|
|
|
|
|
foreach (var consumer in _metadataFactory.Enabled())
|
|
|
|
|
{
|
|
|
|
|
var files = new List<MetadataFile>();
|
|
|
|
|
var consumerFiles = GetMetadataFilesForConsumer(consumer, seriesMetadataFiles);
|
|
|
|
|
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
|
|
|
|
|
|
|
|
|
|
if (message.SeriesFolder.IsNotNullOrWhiteSpace())
|
|
|
|
|
if (seriesFolder.IsNotNullOrWhiteSpace())
|
|
|
|
|
{
|
|
|
|
|
files.AddIfNotNull(ProcessSeriesMetadata(consumer, message.Series, consumerFiles));
|
|
|
|
|
files.AddRange(ProcessSeriesImages(consumer, message.Series, consumerFiles));
|
|
|
|
|
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
|
|
|
|
|
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.SeasonFolder.IsNotNullOrWhiteSpace())
|
|
|
|
|
if (seasonFolder.IsNotNullOrWhiteSpace())
|
|
|
|
|
{
|
|
|
|
|
files.AddRange(ProcessSeasonImages(consumer, message.Series, consumerFiles));
|
|
|
|
|
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_metadataFileService.Upsert(files);
|
|
|
|
|
|
|
|
|
|
return files;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Handle(SeriesRenamedEvent message)
|
|
|
|
|
public override IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles)
|
|
|
|
|
{
|
|
|
|
|
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
|
|
|
|
var episodeFiles = GetEpisodeFiles(message.Series.Id);
|
|
|
|
|
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
|
|
|
|
|
var movedFiles = new List<MetadataFile>();
|
|
|
|
|
|
|
|
|
|
foreach (var consumer in _metadataFactory.Enabled())
|
|
|
|
|
// TODO: Move EpisodeImage and EpisodeMetadata metadata files, instead of relying on consumers to do it
|
|
|
|
|
// (Xbmc's EpisodeImage is more than just the extension)
|
|
|
|
|
|
|
|
|
|
foreach (var consumer in _metadataFactory.GetAvailableProviders())
|
|
|
|
|
{
|
|
|
|
|
var updatedMetadataFiles = consumer.AfterRename(message.Series,
|
|
|
|
|
GetMetadataFilesForConsumer(consumer, seriesMetadata),
|
|
|
|
|
episodeFiles);
|
|
|
|
|
foreach (var episodeFile in episodeFiles)
|
|
|
|
|
{
|
|
|
|
|
var metadataFilesForConsumer = GetMetadataFilesForConsumer(consumer, metadataFiles).Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
|
|
|
|
|
|
|
|
|
|
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
|
|
|
|
|
foreach (var metadataFile in metadataFilesForConsumer)
|
|
|
|
|
{
|
|
|
|
|
var newFileName = consumer.GetFilenameAfterMove(series, episodeFile, metadataFile);
|
|
|
|
|
var existingFileName = Path.Combine(series.Path, metadataFile.RelativePath);
|
|
|
|
|
|
|
|
|
|
if (newFileName.PathNotEquals(existingFileName))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_diskProvider.MoveFile(existingFileName, newFileName);
|
|
|
|
|
metadataFile.RelativePath = series.Path.GetRelativePath(newFileName);
|
|
|
|
|
movedFiles.Add(metadataFile);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.Warn(ex, "Unable to move metadata file: {0}", existingFileName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
|
|
|
|
|
{
|
|
|
|
|
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
|
|
|
|
|
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
|
|
|
|
|
_metadataFileService.Upsert(movedFiles);
|
|
|
|
|
|
|
|
|
|
foreach (var episodeFile in episodeFiles)
|
|
|
|
|
{
|
|
|
|
|
var localEpisodeFile = episodeFile;
|
|
|
|
|
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
|
|
|
|
|
}
|
|
|
|
|
return movedFiles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return episodeFiles;
|
|
|
|
|
public override ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<MetadataFile> GetMetadataFilesForConsumer(IMetadata consumer, List<MetadataFile> seriesMetadata)
|
|
|
|
@ -226,7 +246,7 @@ namespace NzbDrone.Core.Metadata
|
|
|
|
|
if (existingMetadata != null)
|
|
|
|
|
{
|
|
|
|
|
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
|
|
|
|
if (!fullPath.PathEquals(existingFullPath))
|
|
|
|
|
if (fullPath.PathNotEquals(existingFullPath))
|
|
|
|
|
{
|
|
|
|
|
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
|
|
|
|
|
existingMetadata.RelativePath = episodeMetadata.RelativePath;
|
|
|
|
@ -239,6 +259,7 @@ namespace NzbDrone.Core.Metadata
|
|
|
|
|
new MetadataFile
|
|
|
|
|
{
|
|
|
|
|
SeriesId = series.Id,
|
|
|
|
|
SeasonNumber = episodeFile.SeasonNumber,
|
|
|
|
|
EpisodeFileId = episodeFile.Id,
|
|
|
|
|
Consumer = consumer.GetType().Name,
|
|
|
|
|
Type = MetadataType.EpisodeMetadata,
|
|
|
|
@ -347,7 +368,7 @@ namespace NzbDrone.Core.Metadata
|
|
|
|
|
if (existingMetadata != null)
|
|
|
|
|
{
|
|
|
|
|
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
|
|
|
|
|
if (!fullPath.PathEquals(existingFullPath))
|
|
|
|
|
if (fullPath.PathNotEquals(existingFullPath))
|
|
|
|
|
{
|
|
|
|
|
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
|
|
|
|
|
existingMetadata.RelativePath = image.RelativePath;
|
|
|
|
@ -360,6 +381,7 @@ namespace NzbDrone.Core.Metadata
|
|
|
|
|
new MetadataFile
|
|
|
|
|
{
|
|
|
|
|
SeriesId = series.Id,
|
|
|
|
|
SeasonNumber = episodeFile.SeasonNumber,
|
|
|
|
|
EpisodeFileId = episodeFile.Id,
|
|
|
|
|
Consumer = consumer.GetType().Name,
|
|
|
|
|
Type = MetadataType.EpisodeImage,
|