Radarr/src/NzbDrone.Core/Extras/Metadata/MetadataService.cs

455 lines
18 KiB
C#
Raw Normal View History

2014-04-30 23:39:54 +00:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
2014-04-30 23:39:54 +00:00
using System.Net;
using NLog;
2014-04-30 23:39:54 +00:00
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
2014-05-12 02:19:15 +00:00
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Tv;
2014-01-22 05:22:09 +00:00
namespace NzbDrone.Core.Extras.Metadata
2014-01-22 05:22:09 +00:00
{
public class MetadataService : ExtraFileManager<MetadataFile>
2014-01-22 05:22:09 +00:00
{
private readonly IMetadataFactory _metadataFactory;
private readonly ICleanMetadataService _cleanMetadataService;
private readonly IDiskTransferService _diskTransferService;
2014-04-30 23:39:54 +00:00
private readonly IDiskProvider _diskProvider;
2014-09-11 23:49:41 +00:00
private readonly IHttpClient _httpClient;
private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly IMetadataFileService _metadataFileService;
2014-01-22 05:22:09 +00:00
private readonly Logger _logger;
public MetadataService(IConfigService configService,
IDiskTransferService diskTransferService,
IMetadataFactory metadataFactory,
ICleanMetadataService cleanMetadataService,
2014-04-30 23:39:54 +00:00
IDiskProvider diskProvider,
2014-09-11 23:49:41 +00:00
IHttpClient httpClient,
IMediaFileAttributeService mediaFileAttributeService,
IMetadataFileService metadataFileService,
Logger logger)
: base(configService, diskTransferService, metadataFileService)
2014-01-22 05:22:09 +00:00
{
_metadataFactory = metadataFactory;
_cleanMetadataService = cleanMetadataService;
_diskTransferService = diskTransferService;
2014-04-30 23:39:54 +00:00
_diskProvider = diskProvider;
2014-09-11 23:49:41 +00:00
_httpClient = httpClient;
_mediaFileAttributeService = mediaFileAttributeService;
_metadataFileService = metadataFileService;
2014-01-22 05:22:09 +00:00
_logger = logger;
}
2016-12-09 06:54:15 +00:00
public override int Order => 0;
public override IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles)
{
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
_cleanMetadataService.Clean(series);
2014-04-30 23:39:54 +00:00
if (!_diskProvider.FolderExists(series.Path))
2014-04-30 23:39:54 +00:00
{
_logger.Info("Series folder does not exist, skipping metadata creation");
return Enumerable.Empty<MetadataFile>();
2014-04-30 23:39:54 +00:00
}
var files = new List<MetadataFile>();
2014-01-26 07:14:55 +00:00
2014-01-22 05:22:09 +00:00
foreach (var consumer in _metadataFactory.Enabled())
{
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
2014-04-30 23:39:54 +00:00
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
2014-04-30 23:39:54 +00:00
foreach (var episodeFile in episodeFiles)
{
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, consumerFiles));
files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, consumerFiles));
2014-04-30 23:39:54 +00:00
}
2014-01-22 05:22:09 +00:00
}
_metadataFileService.Upsert(files);
return files;
2014-01-22 05:22:09 +00:00
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
2014-01-22 05:22:09 +00:00
{
var files = new List<MetadataFile>();
2014-01-22 05:22:09 +00:00
foreach (var consumer in _metadataFactory.Enabled())
{
2014-04-30 23:39:54 +00:00
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, new List<MetadataFile>()));
files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, new List<MetadataFile>()));
2014-01-22 05:22:09 +00:00
}
_metadataFileService.Upsert(files);
return files;
2014-01-22 05:22:09 +00:00
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, string seriesFolder, string seasonFolder)
{
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
if (seriesFolder.IsNullOrWhiteSpace() && seasonFolder.IsNullOrWhiteSpace())
{
return new List<MetadataFile>();
}
var files = new List<MetadataFile>();
foreach (var consumer in _metadataFactory.Enabled())
{
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
if (seriesFolder.IsNotNullOrWhiteSpace())
{
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
}
if (seasonFolder.IsNotNullOrWhiteSpace())
{
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
}
}
_metadataFileService.Upsert(files);
return files;
}
public override IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles)
2014-01-22 05:22:09 +00:00
{
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
var movedFiles = new List<MetadataFile>();
// 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())
2014-01-22 05:22:09 +00:00
{
foreach (var episodeFile in episodeFiles)
{
var metadataFilesForConsumer = GetMetadataFilesForConsumer(consumer, metadataFiles).Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
2014-04-30 23:39:54 +00:00
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);
}
}
}
}
}
_metadataFileService.Upsert(movedFiles);
return movedFiles;
}
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)
{
return seriesMetadata.Where(c => c.Consumer == consumer.GetType().Name).ToList();
2014-01-22 05:22:09 +00:00
}
2014-04-30 23:39:54 +00:00
private MetadataFile ProcessSeriesMetadata(IMetadata consumer, Series series, List<MetadataFile> existingMetadataFiles)
{
var seriesMetadata = consumer.SeriesMetadata(series);
if (seriesMetadata == null)
{
return null;
}
var hash = seriesMetadata.Contents.SHA256Hash();
var metadata = GetMetadataFile(series, existingMetadataFiles, e => e.Type == MetadataType.SeriesMetadata) ??
2014-04-30 23:39:54 +00:00
new MetadataFile
{
SeriesId = series.Id,
Consumer = consumer.GetType().Name,
Type = MetadataType.SeriesMetadata
2014-04-30 23:39:54 +00:00
};
if (hash == metadata.Hash)
{
if (seriesMetadata.RelativePath != metadata.RelativePath)
{
metadata.RelativePath = seriesMetadata.RelativePath;
return metadata;
}
2014-04-30 23:39:54 +00:00
return null;
}
var fullPath = Path.Combine(series.Path, seriesMetadata.RelativePath);
_logger.Debug("Writing Series Metadata to: {0}", fullPath);
SaveMetadataFile(fullPath, seriesMetadata.Contents);
2014-04-30 23:39:54 +00:00
metadata.Hash = hash;
metadata.RelativePath = seriesMetadata.RelativePath;
2016-09-18 18:30:22 +00:00
metadata.Extension = Path.GetExtension(fullPath);
2014-04-30 23:39:54 +00:00
return metadata;
}
private MetadataFile ProcessEpisodeMetadata(IMetadata consumer, Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
{
var episodeMetadata = consumer.EpisodeMetadata(series, episodeFile);
if (episodeMetadata == null)
{
return null;
}
var fullPath = Path.Combine(series.Path, episodeMetadata.RelativePath);
2014-04-30 23:39:54 +00:00
var existingMetadata = GetMetadataFile(series, existingMetadataFiles, c => c.Type == MetadataType.EpisodeMetadata &&
c.EpisodeFileId == episodeFile.Id);
2014-04-30 23:39:54 +00:00
if (existingMetadata != null)
{
2014-07-23 23:43:54 +00:00
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (fullPath.PathNotEquals(existingFullPath))
2014-04-30 23:39:54 +00:00
{
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
existingMetadata.RelativePath = episodeMetadata.RelativePath;
2014-04-30 23:39:54 +00:00
}
}
var hash = episodeMetadata.Contents.SHA256Hash();
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
SeasonNumber = episodeFile.SeasonNumber,
2014-04-30 23:39:54 +00:00
EpisodeFileId = episodeFile.Id,
Consumer = consumer.GetType().Name,
2014-04-30 23:39:54 +00:00
Type = MetadataType.EpisodeMetadata,
2016-09-18 18:30:22 +00:00
RelativePath = episodeMetadata.RelativePath,
Extension = Path.GetExtension(fullPath)
2014-04-30 23:39:54 +00:00
};
if (hash == metadata.Hash)
{
return null;
}
2014-07-23 23:43:54 +00:00
_logger.Debug("Writing Episode Metadata to: {0}", fullPath);
SaveMetadataFile(fullPath, episodeMetadata.Contents);
2014-04-30 23:39:54 +00:00
metadata.Hash = hash;
return metadata;
}
private List<MetadataFile> ProcessSeriesImages(IMetadata consumer, Series series, List<MetadataFile> existingMetadataFiles)
{
var result = new List<MetadataFile>();
foreach (var image in consumer.SeriesImages(series))
{
var fullPath = Path.Combine(series.Path, image.RelativePath);
if (_diskProvider.FileExists(fullPath))
2014-04-30 23:39:54 +00:00
{
_logger.Debug("Series image already exists: {0}", fullPath);
2014-04-30 23:39:54 +00:00
continue;
}
var metadata = GetMetadataFile(series, existingMetadataFiles, c => c.Type == MetadataType.SeriesImage &&
c.RelativePath == image.RelativePath) ??
2014-04-30 23:39:54 +00:00
new MetadataFile
{
SeriesId = series.Id,
Consumer = consumer.GetType().Name,
2014-04-30 23:39:54 +00:00
Type = MetadataType.SeriesImage,
2016-09-18 18:30:22 +00:00
RelativePath = image.RelativePath,
Extension = Path.GetExtension(fullPath)
2014-04-30 23:39:54 +00:00
};
DownloadImage(series, image);
2014-04-30 23:39:54 +00:00
result.Add(metadata);
}
return result;
}
private List<MetadataFile> ProcessSeasonImages(IMetadata consumer, Series series, List<MetadataFile> existingMetadataFiles)
{
var result = new List<MetadataFile>();
foreach (var season in series.Seasons)
2014-04-30 23:39:54 +00:00
{
foreach (var image in consumer.SeasonImages(series, season))
2014-04-30 23:39:54 +00:00
{
var fullPath = Path.Combine(series.Path, image.RelativePath);
if (_diskProvider.FileExists(fullPath))
2014-04-30 23:39:54 +00:00
{
_logger.Debug("Season image already exists: {0}", fullPath);
2014-04-30 23:39:54 +00:00
continue;
}
var metadata = GetMetadataFile(series, existingMetadataFiles, c => c.Type == MetadataType.SeasonImage &&
c.SeasonNumber == season.SeasonNumber &&
c.RelativePath == image.RelativePath) ??
2014-04-30 23:39:54 +00:00
new MetadataFile
{
SeriesId = series.Id,
SeasonNumber = season.SeasonNumber,
Consumer = consumer.GetType().Name,
2014-04-30 23:39:54 +00:00
Type = MetadataType.SeasonImage,
2016-09-18 18:30:22 +00:00
RelativePath = image.RelativePath,
Extension = Path.GetExtension(fullPath)
2014-04-30 23:39:54 +00:00
};
DownloadImage(series, image);
2014-04-30 23:39:54 +00:00
result.Add(metadata);
}
}
return result;
}
private List<MetadataFile> ProcessEpisodeImages(IMetadata consumer, Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
{
var result = new List<MetadataFile>();
foreach (var image in consumer.EpisodeImages(series, episodeFile))
{
var fullPath = Path.Combine(series.Path, image.RelativePath);
2014-07-23 23:43:54 +00:00
if (_diskProvider.FileExists(fullPath))
2014-04-30 23:39:54 +00:00
{
_logger.Debug("Episode image already exists: {0}", fullPath);
2014-04-30 23:39:54 +00:00
continue;
}
var existingMetadata = GetMetadataFile(series, existingMetadataFiles, c => c.Type == MetadataType.EpisodeImage &&
c.EpisodeFileId == episodeFile.Id);
2014-04-30 23:39:54 +00:00
if (existingMetadata != null)
{
2014-07-23 23:43:54 +00:00
var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (fullPath.PathNotEquals(existingFullPath))
2014-04-30 23:39:54 +00:00
{
_diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move);
existingMetadata.RelativePath = image.RelativePath;
2014-04-30 23:39:54 +00:00
return new List<MetadataFile>{ existingMetadata };
}
}
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
SeasonNumber = episodeFile.SeasonNumber,
2014-04-30 23:39:54 +00:00
EpisodeFileId = episodeFile.Id,
Consumer = consumer.GetType().Name,
2014-04-30 23:39:54 +00:00
Type = MetadataType.EpisodeImage,
2016-09-18 18:30:22 +00:00
RelativePath = image.RelativePath,
Extension = Path.GetExtension(fullPath)
2014-04-30 23:39:54 +00:00
};
DownloadImage(series, image);
2014-04-30 23:39:54 +00:00
result.Add(metadata);
}
return result;
}
private void DownloadImage(Series series, ImageFileResult image)
2014-04-30 23:39:54 +00:00
{
var fullPath = Path.Combine(series.Path, image.RelativePath);
2014-04-30 23:39:54 +00:00
try
{
if (image.Url.StartsWith("http"))
{
_httpClient.DownloadFile(image.Url, fullPath);
}
else
{
_diskProvider.CopyFile(image.Url, fullPath);
}
_mediaFileAttributeService.SetFilePermissions(fullPath);
2014-04-30 23:39:54 +00:00
}
catch (WebException ex)
2014-04-30 23:39:54 +00:00
{
_logger.Warn(ex, "Couldn't download image {0} for {1}. {2}", image.Url, series, ex.Message);
2014-04-30 23:39:54 +00:00
}
catch (Exception ex)
2014-04-30 23:39:54 +00:00
{
_logger.Error(ex, "Couldn't download image {0} for {1}. {2}", image.Url, series, ex.Message);
2014-04-30 23:39:54 +00:00
}
}
private void SaveMetadataFile(string path, string contents)
{
_diskProvider.WriteAllText(path, contents);
_mediaFileAttributeService.SetFilePermissions(path);
}
private MetadataFile GetMetadataFile(Series series, List<MetadataFile> existingMetadataFiles, Func<MetadataFile, bool> predicate)
{
var matchingMetadataFiles = existingMetadataFiles.Where(predicate).ToList();
if (matchingMetadataFiles.Empty())
{
return null;
}
//Remove duplicate metadata files from DB and disk
foreach (var file in matchingMetadataFiles.Skip(1))
{
var path = Path.Combine(series.Path, file.RelativePath);
_logger.Debug("Removing duplicate Metadata file: {0}", path);
_diskProvider.DeleteFile(path);
_metadataFileService.Delete(file.Id);
}
return matchingMetadataFiles.First();
}
2014-01-22 05:22:09 +00:00
}
}