Initial metadata overhaul

This commit is contained in:
Mark McDowall 2014-04-30 16:39:54 -07:00
parent 6d8b38366f
commit 1f40bef249
12 changed files with 648 additions and 827 deletions

View File

@ -12,5 +12,15 @@ namespace NzbDrone.Common
return source.Where(element => knownKeys.Add(keySelector(element)));
}
public static void AddIfNotNull<TSource>(this List<TSource> source, TSource item)
{
if (item == null)
{
return;
}
source.Add(item);
}
}
}

View File

@ -21,117 +21,23 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox
{
public class RoksboxMetadata : MetadataBase<RoksboxMetadataSettings>
{
private readonly IEventAggregator _eventAggregator;
private readonly IMapCoversToLocal _mediaCoverService;
private readonly IMediaFileService _mediaFileService;
private readonly IMetadataFileService _metadataFileService;
private readonly IDiskProvider _diskProvider;
private readonly IHttpProvider _httpProvider;
private readonly IEpisodeService _episodeService;
private readonly Logger _logger;
public RoksboxMetadata(IEventAggregator eventAggregator,
IMapCoversToLocal mediaCoverService,
IMediaFileService mediaFileService,
IMetadataFileService metadataFileService,
public RoksboxMetadata(IMapCoversToLocal mediaCoverService,
IDiskProvider diskProvider,
IHttpProvider httpProvider,
IEpisodeService episodeService,
Logger logger)
: base(diskProvider, httpProvider, logger)
{
_eventAggregator = eventAggregator;
_mediaCoverService = mediaCoverService;
_mediaFileService = mediaFileService;
_metadataFileService = metadataFileService;
_diskProvider = diskProvider;
_httpProvider = httpProvider;
_episodeService = episodeService;
_logger = logger;
}
private static List<string> ValidCertification = new List<string> { "G", "NC-17", "PG", "PG-13", "R", "UR", "UNRATED", "NR", "TV-Y", "TV-Y7", "TV-Y7-FV", "TV-G", "TV-PG", "TV-14", "TV-MA" };
private static readonly Regex SeasonImagesRegex = new Regex(@"^(season (?<season>\d+))|(?<season>specials)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
{
var metadataFiles = new List<MetadataFile>();
if (!_diskProvider.FolderExists(series.Path))
{
_logger.Info("Series folder ({0}) does not exist, skipping metadata creation", series.Path);
return;
}
if (Settings.SeriesImages)
{
var metadata = WriteSeriesImages(series, existingMetadataFiles);
if (metadata != null)
{
metadataFiles.Add(metadata);
}
}
if (Settings.SeasonImages)
{
var metadata = WriteSeasonImages(series, existingMetadataFiles);
if (metadata != null)
{
metadataFiles.AddRange(metadata);
}
}
foreach (var episodeFile in episodeFiles)
{
if (Settings.EpisodeMetadata)
{
var metadata = WriteEpisodeMetadata(series, episodeFile, existingMetadataFiles);
if (metadata != null)
{
metadataFiles.Add(metadata);
}
}
}
foreach (var episodeFile in episodeFiles)
{
if (Settings.EpisodeImages)
{
var metadataFile = WriteEpisodeImages(series, episodeFile, existingMetadataFiles);
if (metadataFile != null)
{
metadataFiles.Add(metadataFile);
}
}
}
metadataFiles.RemoveAll(c => c == null);
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
}
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
{
var metadataFiles = new List<MetadataFile>();
if (Settings.EpisodeMetadata)
{
metadataFiles.Add(WriteEpisodeMetadata(series, episodeFile, new List<MetadataFile>()));
}
if (Settings.EpisodeImages)
{
var metadataFile = WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
if (metadataFile != null)
{
metadataFiles.Add(metadataFile);
}
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
}
public override void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
public override List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
{
var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
var updatedMetadataFiles = new List<MetadataFile>();
@ -172,7 +78,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox
}
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
return updatedMetadataFiles;
}
public override MetadataFile FindMetadataFile(Series series, string path)
@ -237,9 +143,55 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox
return null;
}
private MetadataFile WriteSeriesImages(Series series, List<MetadataFile> existingMetadataFiles)
public override MetadataFileResult SeriesMetadata(Series series)
{
//Series metadata is not supported
return null;
}
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
{
if (!Settings.EpisodeMetadata)
{
return null;
}
_logger.Debug("Generating Episode Metadata for: {0}", episodeFile.Path);
var xmlResult = String.Empty;
foreach (var episode in episodeFile.Episodes.Value)
{
var sb = new StringBuilder();
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = false;
using (var xw = XmlWriter.Create(sb, xws))
{
var doc = new XDocument();
var details = new XElement("video");
details.Add(new XElement("title", String.Format("{0} - {1}x{2} - {3}", series.Title, episode.SeasonNumber, episode.EpisodeNumber, episode.Title)));
details.Add(new XElement("year", episode.AirDate));
details.Add(new XElement("genre", String.Join(" / ", series.Genres)));
var actors = String.Join(" , ", series.Actors.ConvertAll(c => c.Name + " - " + c.Character).GetRange(0, Math.Min(3, series.Actors.Count)));
details.Add(new XElement("actors", actors));
details.Add(new XElement("description", episode.Overview));
details.Add(new XElement("length", series.Runtime));
details.Add(new XElement("mpaa", ValidCertification.Contains(series.Certification.ToUpperInvariant()) ? series.Certification.ToUpperInvariant() : "UNRATED"));
doc.Add(details);
doc.Save(xw);
xmlResult += doc.ToString();
xmlResult += Environment.NewLine;
}
}
return new MetadataFileResult(GetEpisodeMetadataFilename(episodeFile.Path), xmlResult.Trim(Environment.NewLine.ToCharArray()));
}
public override List<ImageFileResult> SeriesImages(Series series)
{
//Because we only support one image, attempt to get the Poster type, then if that fails grab the first
var image = series.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? series.Images.FirstOrDefault();
if (image == null)
{
@ -250,39 +202,66 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
var destination = Path.Combine(series.Path, Path.GetFileName(series.Path) + Path.GetExtension(source));
//TODO: Do we want to overwrite the file if it exists?
if (_diskProvider.FileExists(destination))
{
_logger.Debug("Series image: {0} already exists.", image.CoverType);
return null;
}
else
{
_diskProvider.CopyFile(source, destination, false);
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage) ??
new MetadataFile
{
SeriesId = series.Id,
Consumer = GetType().Name,
Type = MetadataType.SeriesImage,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, destination)
};
return metadata;
}
return new List<ImageFileResult>{ new ImageFileResult(destination, source) };
}
private IEnumerable<MetadataFile> WriteSeasonImages(Series series, List<MetadataFile> existingMetadataFiles)
public override List<ImageFileResult> SeasonImages(Series series, Season season)
{
_logger.Debug("Writing season images for {0}.", series.Title);
//Create a dictionary between season number and output folder
var seasonFolderMap = new Dictionary<int, string>();
foreach (var folder in Directory.EnumerateDirectories(series.Path))
var seasonFolders = GetSeasonFolders(series);
string seasonFolder;
if (!seasonFolders.TryGetValue(season.SeasonNumber, out seasonFolder))
{
_logger.Trace("Failed to find season folder for series {0}, season {1}.", series.Title, season.SeasonNumber);
return new List<ImageFileResult>();
}
//Roksbox only supports one season image, so first of all try for poster otherwise just use whatever is first in the collection
var image = season.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? season.Images.FirstOrDefault();
if (image == null)
{
_logger.Trace("Failed to find suitable season image for series {0}, season {1}.", series.Title, season.SeasonNumber);
return new List<ImageFileResult>();
}
var filename = Path.GetFileName(seasonFolder) + ".jpg";
var path = Path.Combine(series.Path, seasonFolder, filename);
return new List<ImageFileResult> { new ImageFileResult(path, image.Url) };
}
public override List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile)
{
var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
if (screenshot == null)
{
_logger.Trace("Episode screenshot not available");
return new List<ImageFileResult>();
}
return new List<ImageFileResult> {new ImageFileResult(GetEpisodeImageFilename(episodeFile.Path), screenshot.Url)};
}
private string GetEpisodeMetadataFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "xml");
}
private string GetEpisodeImageFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "jpg");
}
private Dictionary<Int32, String> GetSeasonFolders(Series series)
{
var seasonFolderMap = new Dictionary<Int32, String>();
foreach (var folder in _diskProvider.GetDirectories(series.Path))
{
var directoryinfo = new DirectoryInfo(folder);
var seasonMatch = SeasonImagesRegex.Match(directoryinfo.Name);
if (seasonMatch.Success)
{
var seasonNumber = seasonMatch.Groups["season"].Value;
@ -309,160 +288,8 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox
_logger.Debug("Rejecting folder {0} for series {1}.", Path.GetDirectoryName(folder), series.Title);
}
}
foreach (var season in series.Seasons)
{
//Work out the path to this season - if we don't have a matching path then skip this season.
string seasonFolder;
if (!seasonFolderMap.TryGetValue(season.SeasonNumber, out seasonFolder))
{
_logger.Trace("Failed to find season folder for series {0}, season {1}.", series.Title, season.SeasonNumber);
continue;
}
//Roksbox only supports one season image, so first of all try for poster otherwise just use whatever is first in the collection
var image = season.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? season.Images.FirstOrDefault();
if (image == null)
{
_logger.Trace("Failed to find suitable season image for series {0}, season {1}.", series.Title, season.SeasonNumber);
continue;
}
var filename = Path.GetFileName(seasonFolder) + ".jpg";
var path = Path.Combine(series.Path, seasonFolder, filename);
_logger.Debug("Writing season image for series {0}, season {1} to {2}.", series.Title, season.SeasonNumber, path);
DownloadImage(series, image.Url, path);
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeasonImage &&
c.SeasonNumber == season.SeasonNumber) ??
new MetadataFile
{
SeriesId = series.Id,
SeasonNumber = season.SeasonNumber,
Consumer = GetType().Name,
Type = MetadataType.SeasonImage,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
};
yield return metadata;
}
}
private MetadataFile WriteEpisodeMetadata(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
{
var filename = GetEpisodeMetadataFilename(episodeFile.Path);
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
c.EpisodeFileId == episodeFile.Id);
if (existingMetadata != null)
{
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!filename.PathEquals(fullPath))
{
_diskProvider.MoveFile(fullPath, filename);
existingMetadata.RelativePath = relativePath;
}
}
_logger.Debug("Generating {0} for: {1}", filename, episodeFile.Path);
var xmlResult = String.Empty;
foreach (var episode in episodeFile.Episodes.Value)
{
var sb = new StringBuilder();
var xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = false;
using (var xw = XmlWriter.Create(sb, xws))
{
var doc = new XDocument();
var details = new XElement("video");
details.Add(new XElement("title", String.Format("{0} - {1}x{2} - {3}", series.Title, episode.SeasonNumber, episode.EpisodeNumber, episode.Title)));
details.Add(new XElement("year", episode.AirDate));
details.Add(new XElement("genre", String.Join(" / ", series.Genres)));
var actors = String.Join(" , ", series.Actors.ConvertAll(c => c.Name + " - " + c.Character).GetRange(0, Math.Min(3, series.Actors.Count)));
details.Add(new XElement("actors", actors));
details.Add(new XElement("description", episode.Overview));
details.Add(new XElement("length", series.Runtime));
details.Add(new XElement("mpaa", ValidCertification.Contains( series.Certification.ToUpperInvariant() ) ? series.Certification.ToUpperInvariant() : "UNRATED" ) );
doc.Add(details);
doc.Save(xw);
xmlResult += doc.ToString();
xmlResult += Environment.NewLine;
}
}
_logger.Debug("Saving episodedetails to: {0}", filename);
_diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
EpisodeFileId = episodeFile.Id,
Consumer = GetType().Name,
Type = MetadataType.EpisodeMetadata,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
};
return metadata;
}
private MetadataFile WriteEpisodeImages(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
{
var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
if (screenshot == null)
{
_logger.Trace("Episode screenshot not available");
return null;
}
var filename = GetEpisodeImageFilename(episodeFile.Path);
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage &&
c.EpisodeFileId == episodeFile.Id);
if (existingMetadata != null)
{
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!filename.PathEquals(fullPath))
{
_diskProvider.MoveFile(fullPath, filename);
existingMetadata.RelativePath = relativePath;
}
}
DownloadImage(series, screenshot.Url, filename);
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
EpisodeFileId = episodeFile.Id,
Consumer = GetType().Name,
Type = MetadataType.EpisodeImage,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
};
return metadata;
}
private string GetEpisodeMetadataFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "xml");
}
private string GetEpisodeImageFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "jpg");
return seasonFolderMap;
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.Remoting.Messaging;
@ -21,116 +22,22 @@ namespace NzbDrone.Core.Metadata.Consumers.Wdtv
{
public class WdtvMetadata : MetadataBase<WdtvMetadataSettings>
{
private readonly IEventAggregator _eventAggregator;
private readonly IMapCoversToLocal _mediaCoverService;
private readonly IMediaFileService _mediaFileService;
private readonly IMetadataFileService _metadataFileService;
private readonly IDiskProvider _diskProvider;
private readonly IHttpProvider _httpProvider;
private readonly IEpisodeService _episodeService;
private readonly Logger _logger;
public WdtvMetadata(IEventAggregator eventAggregator,
IMapCoversToLocal mediaCoverService,
IMediaFileService mediaFileService,
IMetadataFileService metadataFileService,
public WdtvMetadata(IMapCoversToLocal mediaCoverService,
IDiskProvider diskProvider,
IHttpProvider httpProvider,
IEpisodeService episodeService,
Logger logger)
: base(diskProvider, httpProvider, logger)
{
_eventAggregator = eventAggregator;
_mediaCoverService = mediaCoverService;
_mediaFileService = mediaFileService;
_metadataFileService = metadataFileService;
_diskProvider = diskProvider;
_httpProvider = httpProvider;
_episodeService = episodeService;
_logger = logger;
}
private static readonly Regex SeasonImagesRegex = new Regex(@"^(season (?<season>\d+))|(?<season>specials)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
{
var metadataFiles = new List<MetadataFile>();
if (!_diskProvider.FolderExists(series.Path))
{
_logger.Info("Series folder ({0}) does not exist, skipping metadata creation", series.Path);
return;
}
if (Settings.SeriesImages)
{
var metadata = WriteSeriesImages(series, existingMetadataFiles);
if (metadata != null)
{
metadataFiles.Add(metadata);
}
}
if (Settings.SeasonImages)
{
var metadata = WriteSeasonImages(series, existingMetadataFiles);
if (metadata != null)
{
metadataFiles.AddRange(metadata);
}
}
foreach (var episodeFile in episodeFiles)
{
if (Settings.EpisodeMetadata)
{
var metadata = WriteEpisodeMetadata(series, episodeFile, existingMetadataFiles);
if (metadata != null)
{
metadataFiles.Add(metadata);
}
}
}
foreach (var episodeFile in episodeFiles)
{
if (Settings.EpisodeImages)
{
var metadataFile = WriteEpisodeImages(series, episodeFile, existingMetadataFiles);
if (metadataFile != null)
{
metadataFiles.Add(metadataFile);
}
}
}
metadataFiles.RemoveAll(c => c == null);
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
}
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
{
var metadataFiles = new List<MetadataFile>();
if (Settings.EpisodeMetadata)
{
metadataFiles.Add(WriteEpisodeMetadata(series, episodeFile, new List<MetadataFile>()));
}
if (Settings.EpisodeImages)
{
var metadataFile = WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
if (metadataFile != null)
{
metadataFiles.Add(metadataFile);
}
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
}
public override void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
public override List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
{
var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
var updatedMetadataFiles = new List<MetadataFile>();
@ -170,8 +77,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Wdtv
}
}
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
return updatedMetadataFiles;
}
public override MetadataFile FindMetadataFile(Series series, string path)
@ -236,137 +142,20 @@ namespace NzbDrone.Core.Metadata.Consumers.Wdtv
return null;
}
private MetadataFile WriteSeriesImages(Series series, List<MetadataFile> existingMetadataFiles)
public override MetadataFileResult SeriesMetadata(Series series)
{
//Because we only support one image, attempt to get the Poster type, then if that fails grab the first
var image = series.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? series.Images.FirstOrDefault();
if (image == null)
//Series metadata is not supported
return null;
}
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
{
if (!Settings.EpisodeMetadata)
{
_logger.Trace("Failed to find suitable Series image for series {0}.", series.Title);
return null;
}
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
var destination = Path.Combine(series.Path, "folder" + Path.GetExtension(source));
//TODO: Do we want to overwrite the file if it exists?
if (_diskProvider.FileExists(destination))
{
_logger.Debug("Series image: {0} already exists.", image.CoverType);
return null;
}
else
{
_diskProvider.CopyFile(source, destination, false);
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage) ??
new MetadataFile
{
SeriesId = series.Id,
Consumer = GetType().Name,
Type = MetadataType.SeriesImage,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, destination)
};
return metadata;
}
}
private IEnumerable<MetadataFile> WriteSeasonImages(Series series, List<MetadataFile> existingMetadataFiles)
{
_logger.Debug("Writing season images for {0}.", series.Title);
//Create a dictionary between season number and output folder
var seasonFolderMap = new Dictionary<int, string>();
foreach (var folder in Directory.EnumerateDirectories(series.Path))
{
var directoryinfo = new DirectoryInfo(folder);
var seasonMatch = SeasonImagesRegex.Match(directoryinfo.Name);
if (seasonMatch.Success)
{
var seasonNumber = seasonMatch.Groups["season"].Value;
if (seasonNumber.Contains("specials"))
{
seasonFolderMap[0] = folder;
}
else
{
int matchedSeason;
if (Int32.TryParse(seasonNumber, out matchedSeason))
{
seasonFolderMap[matchedSeason] = folder;
}
else
{
_logger.Debug("Failed to parse season number from {0} for series {1}.", folder, series.Title);
}
}
}
else
{
_logger.Debug("Rejecting folder {0} for series {1}.", Path.GetDirectoryName(folder), series.Title);
}
}
foreach (var season in series.Seasons)
{
//Work out the path to this season - if we don't have a matching path then skip this season.
string seasonFolder;
if (!seasonFolderMap.TryGetValue(season.SeasonNumber, out seasonFolder))
{
_logger.Trace("Failed to find season folder for series {0}, season {1}.", series.Title, season.SeasonNumber);
continue;
}
//WDTV only supports one season image, so first of all try for poster otherwise just use whatever is first in the collection
var image = season.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? season.Images.FirstOrDefault();
if (image == null)
{
_logger.Trace("Failed to find suitable season image for series {0}, season {1}.", series.Title, season.SeasonNumber);
continue;
}
var filename = "folder.jpg";
var path = Path.Combine(series.Path, seasonFolder, filename);
_logger.Debug("Writing season image for series {0}, season {1} to {2}.", series.Title, season.SeasonNumber, path);
DownloadImage(series, image.Url, path);
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeasonImage &&
c.SeasonNumber == season.SeasonNumber) ??
new MetadataFile
{
SeriesId = series.Id,
SeasonNumber = season.SeasonNumber,
Consumer = GetType().Name,
Type = MetadataType.SeasonImage,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
};
yield return metadata;
}
}
private MetadataFile WriteEpisodeMetadata(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
{
var filename = GetEpisodeMetadataFilename(episodeFile.Path);
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
c.EpisodeFileId == episodeFile.Id);
if (existingMetadata != null)
{
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!filename.PathEquals(fullPath))
{
_diskProvider.MoveFile(fullPath, filename);
existingMetadata.RelativePath = relativePath;
}
}
_logger.Debug("Generating {0} for: {1}", filename, episodeFile.Path);
_logger.Debug("Generating Episode Metadata for: {0}", episodeFile.Path);
var xmlResult = String.Empty;
foreach (var episode in episodeFile.Episodes.Value)
@ -404,62 +193,82 @@ namespace NzbDrone.Core.Metadata.Consumers.Wdtv
xmlResult += Environment.NewLine;
}
}
_logger.Debug("Saving episodedetails to: {0}", filename);
_diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
EpisodeFileId = episodeFile.Id,
Consumer = GetType().Name,
Type = MetadataType.EpisodeMetadata,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
};
var filename = GetEpisodeMetadataFilename(episodeFile.Path);
return metadata;
return new MetadataFileResult(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
}
private MetadataFile WriteEpisodeImages(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
public override List<ImageFileResult> SeriesImages(Series series)
{
if (!Settings.SeriesImages)
{
return new List<ImageFileResult>();
}
//Because we only support one image, attempt to get the Poster type, then if that fails grab the first
var image = series.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? series.Images.FirstOrDefault();
if (image == null)
{
_logger.Trace("Failed to find suitable Series image for series {0}.", series.Title);
return new List<ImageFileResult>();
}
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
var destination = Path.Combine(series.Path, "folder" + Path.GetExtension(source));
return new List<ImageFileResult>
{
new ImageFileResult(destination, source)
};
}
public override List<ImageFileResult> SeasonImages(Series series, Season season)
{
if (!Settings.SeasonImages)
{
return new List<ImageFileResult>();
}
var seasonFolders = GetSeasonFolders(series);
//Work out the path to this season - if we don't have a matching path then skip this season.
string seasonFolder;
if (!seasonFolders.TryGetValue(season.SeasonNumber, out seasonFolder))
{
_logger.Trace("Failed to find season folder for series {0}, season {1}.", series.Title, season.SeasonNumber);
return new List<ImageFileResult>();
}
//WDTV only supports one season image, so first of all try for poster otherwise just use whatever is first in the collection
var image = season.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? season.Images.FirstOrDefault();
if (image == null)
{
_logger.Trace("Failed to find suitable season image for series {0}, season {1}.", series.Title, season.SeasonNumber);
return new List<ImageFileResult>();
}
var path = Path.Combine(series.Path, seasonFolder, "folder.jpg");
return new List<ImageFileResult>{ new ImageFileResult(path, image.Url) };
}
public override List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile)
{
if (!Settings.EpisodeImages)
{
return new List<ImageFileResult>();
}
var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
if (screenshot == null)
{
_logger.Trace("Episode screenshot not available");
return null;
return new List<ImageFileResult>();
}
var filename = GetEpisodeImageFilename(episodeFile.Path);
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage &&
c.EpisodeFileId == episodeFile.Id);
if (existingMetadata != null)
{
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!filename.PathEquals(fullPath))
{
_diskProvider.MoveFile(fullPath, filename);
existingMetadata.RelativePath = relativePath;
}
}
DownloadImage(series, screenshot.Url, filename);
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
EpisodeFileId = episodeFile.Id,
Consumer = GetType().Name,
Type = MetadataType.EpisodeImage,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
};
return metadata;
return new List<ImageFileResult>{ new ImageFileResult(GetEpisodeImageFilename(episodeFile.Path), screenshot.Url) };
}
private string GetEpisodeMetadataFilename(string episodeFilePath)
@ -471,5 +280,45 @@ namespace NzbDrone.Core.Metadata.Consumers.Wdtv
{
return Path.ChangeExtension(episodeFilePath, "metathumb");
}
private Dictionary<Int32, String> GetSeasonFolders(Series series)
{
var seasonFolderMap = new Dictionary<Int32, String>();
foreach (var folder in _diskProvider.GetDirectories(series.Path))
{
var directoryinfo = new DirectoryInfo(folder);
var seasonMatch = SeasonImagesRegex.Match(directoryinfo.Name);
if (seasonMatch.Success)
{
var seasonNumber = seasonMatch.Groups["season"].Value;
if (seasonNumber.Contains("specials"))
{
seasonFolderMap[0] = folder;
}
else
{
int matchedSeason;
if (Int32.TryParse(seasonNumber, out matchedSeason))
{
seasonFolderMap[matchedSeason] = folder;
}
else
{
_logger.Debug("Failed to parse season number from {0} for series {1}.", folder, series.Title);
}
}
}
else
{
_logger.Debug("Rejecting folder {0} for series {1}.", Path.GetDirectoryName(folder), series.Title);
}
}
return seasonFolderMap;
}
}
}

View File

@ -19,32 +19,16 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
{
public class XbmcMetadata : MetadataBase<XbmcMetadataSettings>
{
private readonly IEventAggregator _eventAggregator;
private readonly IMapCoversToLocal _mediaCoverService;
private readonly IMediaFileService _mediaFileService;
private readonly IMetadataFileService _metadataFileService;
private readonly IDiskProvider _diskProvider;
private readonly IHttpProvider _httpProvider;
private readonly IEpisodeService _episodeService;
private readonly Logger _logger;
public XbmcMetadata(IEventAggregator eventAggregator,
IMapCoversToLocal mediaCoverService,
IMediaFileService mediaFileService,
IMetadataFileService metadataFileService,
public XbmcMetadata(IMapCoversToLocal mediaCoverService,
IDiskProvider diskProvider,
IHttpProvider httpProvider,
IEpisodeService episodeService,
Logger logger)
: base(diskProvider, httpProvider, logger)
{
_eventAggregator = eventAggregator;
_mediaCoverService = mediaCoverService;
_mediaFileService = mediaFileService;
_metadataFileService = metadataFileService;
_diskProvider = diskProvider;
_httpProvider = httpProvider;
_episodeService = episodeService;
_logger = logger;
}
@ -52,79 +36,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
private static readonly Regex SeasonImagesRegex = new Regex(@"^season(?<season>\d{2,}|-all|-specials)-(?<type>poster|banner|fanart)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex EpisodeImageRegex = new Regex(@"-thumb\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
{
var metadataFiles = new List<MetadataFile>();
if (!_diskProvider.FolderExists(series.Path))
{
_logger.Info("Series folder does not exist, skipping metadata creation");
return;
}
if (Settings.SeriesMetadata)
{
metadataFiles.Add(WriteTvShowNfo(series, existingMetadataFiles));
}
if (Settings.SeriesImages)
{
metadataFiles.AddRange(WriteSeriesImages(series, existingMetadataFiles));
}
if (Settings.SeasonImages)
{
metadataFiles.AddRange(WriteSeasonImages(series, existingMetadataFiles));
}
foreach (var episodeFile in episodeFiles)
{
if (Settings.EpisodeMetadata)
{
metadataFiles.Add(WriteEpisodeNfo(series, episodeFile, existingMetadataFiles));
}
}
foreach (var episodeFile in episodeFiles)
{
if (Settings.EpisodeImages)
{
var metadataFile = WriteEpisodeImages(series, episodeFile, existingMetadataFiles);
if (metadataFile != null)
{
metadataFiles.Add(metadataFile);
}
}
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
}
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
{
var metadataFiles = new List<MetadataFile>();
if (Settings.EpisodeMetadata)
{
metadataFiles.Add(WriteEpisodeNfo(series, episodeFile, new List<MetadataFile>()));
}
if (Settings.EpisodeImages)
{
var metadataFile = WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
if (metadataFile != null)
{
metadataFiles.Add(metadataFile);
}
WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
}
public override void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
public override List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
{
var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
var updatedMetadataFiles = new List<MetadataFile>();
@ -165,7 +77,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
}
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
return updatedMetadataFiles;
}
public override MetadataFile FindMetadataFile(Series series, string path)
@ -239,8 +151,13 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
return null;
}
private MetadataFile WriteTvShowNfo(Series series, List<MetadataFile> existingMetadataFiles)
public override MetadataFileResult SeriesMetadata(Series series)
{
if (!Settings.SeriesMetadata)
{
return null;
}
_logger.Debug("Generating tvshow.nfo for: {0}", series.Title);
var sb = new StringBuilder();
var xws = new XmlWriterSettings();
@ -254,7 +171,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
var tvShow = new XElement("tvshow");
tvShow.Add(new XElement("title", series.Title));
tvShow.Add(new XElement("rating", (decimal)series.Ratings.Percentage/10));
tvShow.Add(new XElement("rating", (decimal) series.Ratings.Percentage/10));
tvShow.Add(new XElement("plot", series.Overview));
tvShow.Add(new XElement("episodeguide", new XElement("url", episodeGuideUrl)));
tvShow.Add(new XElement("episodeguideurl", episodeGuideUrl));
@ -276,10 +193,10 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
foreach (var actor in series.Actors)
{
tvShow.Add(new XElement("actor",
new XElement("name", actor.Name),
new XElement("role", actor.Character),
new XElement("thumb", actor.Images.First().Url)
));
new XElement("name", actor.Name),
new XElement("role", actor.Character),
new XElement("thumb", actor.Images.First().Url)
));
}
var doc = new XDocument(tvShow);
@ -287,108 +204,13 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
_logger.Debug("Saving tvshow.nfo for {0}", series.Title);
var path = Path.Combine(series.Path, "tvshow.nfo");
_diskProvider.WriteAllText(path, doc.ToString());
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesMetadata) ??
new MetadataFile
{
SeriesId = series.Id,
Consumer = GetType().Name,
Type = MetadataType.SeriesMetadata,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
};
return metadata;
return new MetadataFileResult(Path.Combine(series.Path, "tvshow.nfo"), doc.ToString());
}
}
private IEnumerable<MetadataFile> WriteSeriesImages(Series series, List<MetadataFile> existingMetadataFiles)
{
foreach (var image in series.Images)
{
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
var destination = Path.Combine(series.Path, image.CoverType.ToString().ToLowerInvariant() + Path.GetExtension(source));
//TODO: Do we want to overwrite the file if it exists?
if (_diskProvider.FileExists(destination))
{
_logger.Debug("Series image: {0} already exists.", image.CoverType);
continue;
}
_diskProvider.CopyFile(source, destination, false);
var relativePath = DiskProviderBase.GetRelativePath(series.Path, destination);
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage &&
c.RelativePath == relativePath) ??
new MetadataFile
{
SeriesId = series.Id,
Consumer = GetType().Name,
Type = MetadataType.SeriesImage,
RelativePath = relativePath
};
yield return metadata;
}
}
private IEnumerable<MetadataFile> WriteSeasonImages(Series series, List<MetadataFile> existingMetadataFiles)
{
foreach (var season in series.Seasons)
{
foreach (var image in season.Images)
{
var filename = String.Format("season{0:00}-{1}.jpg", season.SeasonNumber, image.CoverType.ToString().ToLower());
if (season.SeasonNumber == 0)
{
filename = String.Format("season-specials-{0}.jpg", image.CoverType.ToString().ToLower());
}
var path = Path.Combine(series.Path, filename);
var relativePath = DiskProviderBase.GetRelativePath(series.Path, path);
DownloadImage(series, image.Url, path);
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeasonImage &&
c.SeasonNumber == season.SeasonNumber &&
c.RelativePath == relativePath) ??
new MetadataFile
{
SeriesId = series.Id,
SeasonNumber = season.SeasonNumber,
Consumer = GetType().Name,
Type = MetadataType.SeasonImage,
RelativePath = relativePath
};
yield return metadata;
}
}
}
private MetadataFile WriteEpisodeNfo(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
{
var filename = GetEpisodeNfoFilename(episodeFile.Path);
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
c.EpisodeFileId == episodeFile.Id);
if (existingMetadata != null)
{
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!filename.PathEquals(fullPath))
{
_diskProvider.MoveFile(fullPath, filename);
existingMetadata.RelativePath = relativePath;
}
}
_logger.Debug("Generating {0} for: {1}", filename, episodeFile.Path);
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile)
{
_logger.Debug("Generating Episode Metadata for: {0}", episodeFile.Path);
var xmlResult = String.Empty;
foreach (var episode in episodeFile.Episodes.Value)
@ -423,9 +245,9 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
{
details.Add(new XElement("thumb", image.Url));
}
details.Add(new XElement("watched", "false"));
details.Add(new XElement("rating", (decimal)episode.Ratings.Percentage/10));
details.Add(new XElement("rating", (decimal)episode.Ratings.Percentage / 10));
//Todo: get guest stars, writer and director
//details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault()));
@ -438,25 +260,37 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
xmlResult += Environment.NewLine;
}
}
_logger.Debug("Saving episodedetails to: {0}", filename);
_diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
EpisodeFileId = episodeFile.Id,
Consumer = GetType().Name,
Type = MetadataType.EpisodeMetadata,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
};
return metadata;
return new MetadataFileResult(GetEpisodeNfoFilename(episodeFile.Path), xmlResult.Trim(Environment.NewLine.ToCharArray()));
}
private MetadataFile WriteEpisodeImages(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
public override List<ImageFileResult> SeriesImages(Series series)
{
if (!Settings.SeriesImages)
{
return new List<ImageFileResult>();
}
return ProcessSeriesImages(series).ToList();
}
public override List<ImageFileResult> SeasonImages(Series series, Season season)
{
if (!Settings.SeasonImages)
{
return new List<ImageFileResult>();
}
return ProcessSeasonImages(series, season).ToList();
}
public override List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile)
{
if (!Settings.EpisodeImages)
{
return new List<ImageFileResult>();
}
var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
if (screenshot == null)
@ -465,35 +299,38 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
return null;
}
var filename = GetEpisodeImageFilename(episodeFile.Path);
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
return new List<ImageFileResult>
{
new ImageFileResult(GetEpisodeImageFilename(episodeFile.Path), screenshot.Url)
};
}
var existingMetadata = existingMetadataFiles.FirstOrDefault(c => c.Type == MetadataType.EpisodeImage &&
c.EpisodeFileId == episodeFile.Id);
if (existingMetadata != null)
private IEnumerable<ImageFileResult> ProcessSeriesImages(Series series)
{
foreach (var image in series.Images)
{
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!filename.PathEquals(fullPath))
{
_diskProvider.MoveFile(fullPath, filename);
existingMetadata.RelativePath = relativePath;
}
var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType);
var destination = Path.Combine(series.Path, image.CoverType.ToString().ToLowerInvariant() + Path.GetExtension(source));
yield return new ImageFileResult(destination, source);
}
}
DownloadImage(series, screenshot.Url, filename);
private IEnumerable<ImageFileResult> ProcessSeasonImages(Series series, Season season)
{
foreach (var image in season.Images)
{
var filename = String.Format("season{0:00}-{1}.jpg", season.SeasonNumber, image.CoverType.ToString().ToLower());
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
EpisodeFileId = episodeFile.Id,
Consumer = GetType().Name,
Type = MetadataType.EpisodeImage,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
};
if (season.SeasonNumber == 0)
{
filename = String.Format("season-specials-{0}.jpg", image.CoverType.ToString().ToLower());
}
return metadata;
var path = Path.Combine(series.Path, filename);
yield return new ImageFileResult(Path.Combine(series.Path, filename), image.Url);
}
}
private string GetEpisodeNfoFilename(string episodeFilePath)

View File

@ -0,0 +1,16 @@
using System;
namespace NzbDrone.Core.Metadata.Files
{
public class ImageFileResult
{
public String Path { get; set; }
public String Url { get; set; }
public ImageFileResult(string path, string url)
{
Path = path;
Url = url;
}
}
}

View File

@ -0,0 +1,16 @@
using System;
namespace NzbDrone.Core.Metadata.Files
{
public class MetadataFileResult
{
public String Path { get; set; }
public String Contents { get; set; }
public MetadataFileResult(string path, string contents)
{
Path = path;
Contents = contents;
}
}
}

View File

@ -8,9 +8,14 @@ namespace NzbDrone.Core.Metadata
{
public interface IMetadata : IProvider
{
void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload);
void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
MetadataFile FindMetadataFile(Series series, string path);
MetadataFileResult SeriesMetadata(Series series);
MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
List<ImageFileResult> SeriesImages(Series series);
List<ImageFileResult> SeasonImages(Series series, Season season);
List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile);
}
}

View File

@ -1,6 +1,12 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
@ -21,6 +27,9 @@ namespace NzbDrone.Core.Metadata
private readonly ICleanMetadataService _cleanMetadataService;
private readonly IMediaFileService _mediaFileService;
private readonly IEpisodeService _episodeService;
private readonly IDiskProvider _diskProvider;
private readonly IHttpProvider _httpProvider;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public MetadataService(IMetadataFactory metadataFactory,
@ -28,6 +37,9 @@ namespace NzbDrone.Core.Metadata
ICleanMetadataService cleanMetadataService,
IMediaFileService mediaFileService,
IEpisodeService episodeService,
IDiskProvider diskProvider,
IHttpProvider httpProvider,
IEventAggregator eventAggregator,
Logger logger)
{
_metadataFactory = metadataFactory;
@ -35,17 +47,41 @@ namespace NzbDrone.Core.Metadata
_cleanMetadataService = cleanMetadataService;
_mediaFileService = mediaFileService;
_episodeService = episodeService;
_diskProvider = diskProvider;
_httpProvider = httpProvider;
_eventAggregator = eventAggregator;
_logger = logger;
}
public void Handle(MediaCoversUpdatedEvent message)
{
_cleanMetadataService.Clean(message.Series);
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
if (!_diskProvider.FolderExists(message.Series.Path))
{
_logger.Info("Series folder does not exist, skipping metadata creation");
return;
}
var seriesMetadataFiles = _metadataFileService.GetFilesBySeries(message.Series.Id);
var episodeFiles = GetEpisodeFiles(message.Series.Id);
foreach (var consumer in _metadataFactory.Enabled())
{
consumer.OnSeriesUpdated(message.Series, GetMetadataFilesForConsumer(consumer, seriesMetadata), GetEpisodeFiles(message.Series.Id));
var consumerFiles = GetMetadataFilesForConsumer(consumer, seriesMetadataFiles);
var files = new List<MetadataFile>();
files.AddIfNotNull(ProcessSeriesMetadata(consumer, message.Series, consumerFiles));
files.AddRange(ProcessSeriesImages(consumer, message.Series, consumerFiles));
files.AddRange(ProcessSeasonImages(consumer, message.Series, consumerFiles));
foreach (var episodeFile in episodeFiles)
{
files.AddIfNotNull(ProcessEpisodeMetadata(consumer, message.Series, episodeFile, consumerFiles));
files.AddRange(ProcessEpisodeImages(consumer, message.Series, episodeFile, consumerFiles));
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(files));
}
}
@ -53,17 +89,27 @@ namespace NzbDrone.Core.Metadata
{
foreach (var consumer in _metadataFactory.Enabled())
{
consumer.OnEpisodeImport(message.EpisodeInfo.Series, message.ImportedEpisode, message.NewDownload);
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));
}
}
public void Handle(SeriesRenamedEvent message)
{
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
var episodeFiles = GetEpisodeFiles(message.Series.Id);
foreach (var consumer in _metadataFactory.Enabled())
{
consumer.AfterRename(message.Series, GetMetadataFilesForConsumer(consumer, seriesMetadata), GetEpisodeFiles(message.Series.Id));
var updatedMetadataFiles = consumer.AfterRename(message.Series,
GetMetadataFilesForConsumer(consumer, seriesMetadata),
episodeFiles);
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
}
}
@ -85,5 +131,219 @@ namespace NzbDrone.Core.Metadata
{
return seriesMetadata.Where(c => c.Consumer == consumer.GetType().Name).ToList();
}
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 = existingMetadataFiles.SingleOrDefault(e => e.Type == MetadataType.SeriesMetadata) ??
new MetadataFile
{
SeriesId = series.Id,
Consumer = GetType().Name,
Type = MetadataType.SeriesMetadata,
};
if (hash == metadata.Hash)
{
return null;
}
_logger.Debug("Writing Series Metadata to: {0}", seriesMetadata.Path);
_diskProvider.WriteAllText(seriesMetadata.Path, seriesMetadata.Contents);
metadata.Hash = hash;
metadata.RelativePath = DiskProviderBase.GetRelativePath(series.Path, seriesMetadata.Path);
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 relativePath = DiskProviderBase.GetRelativePath(series.Path, episodeMetadata.Path);
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
c.EpisodeFileId == episodeFile.Id);
if (existingMetadata != null)
{
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!episodeMetadata.Path.PathEquals(fullPath))
{
_diskProvider.MoveFile(fullPath, episodeMetadata.Path);
existingMetadata.RelativePath = relativePath;
}
}
var hash = episodeMetadata.Contents.SHA256Hash();
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
EpisodeFileId = episodeFile.Id,
Consumer = GetType().Name,
Type = MetadataType.EpisodeMetadata,
RelativePath = relativePath
};
if (hash == metadata.Hash)
{
return null;
}
_logger.Debug("Writing Episode Metadata to: {0}", episodeMetadata.Path);
_diskProvider.WriteAllText(episodeMetadata.Path, episodeMetadata.Contents);
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))
{
if (_diskProvider.FileExists(image.Path))
{
_logger.Debug("Series image already exists: {0}", image.Path);
continue;
}
var relativePath = DiskProviderBase.GetRelativePath(series.Path, image.Path);
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeriesImage &&
c.RelativePath == relativePath) ??
new MetadataFile
{
SeriesId = series.Id,
Consumer = GetType().Name,
Type = MetadataType.SeriesImage,
RelativePath = relativePath
};
_diskProvider.CopyFile(image.Url, image.Path);
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)
{
foreach (var image in consumer.SeasonImages(series, season))
{
if (_diskProvider.FileExists(image.Path))
{
_logger.Debug("Season image already exists: {0}", image.Path);
continue;
}
var relativePath = DiskProviderBase.GetRelativePath(series.Path, image.Path);
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.SeasonImage &&
c.SeasonNumber == season.SeasonNumber &&
c.RelativePath == relativePath) ??
new MetadataFile
{
SeriesId = series.Id,
SeasonNumber = season.SeasonNumber,
Consumer = GetType().Name,
Type = MetadataType.SeasonImage,
RelativePath = relativePath
};
DownloadImage(series, image.Url, image.Path);
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))
{
if (_diskProvider.FileExists(image.Path))
{
_logger.Debug("Episode image already exists: {0}", image.Path);
continue;
}
var relativePath = DiskProviderBase.GetRelativePath(series.Path, image.Path);
var existingMetadata = existingMetadataFiles.FirstOrDefault(c => c.Type == MetadataType.EpisodeImage &&
c.EpisodeFileId == episodeFile.Id);
if (existingMetadata != null)
{
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!image.Path.PathEquals(fullPath))
{
_diskProvider.MoveFile(fullPath, image.Path);
existingMetadata.RelativePath = relativePath;
return new List<MetadataFile>{ existingMetadata };
}
}
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
EpisodeFileId = episodeFile.Id,
Consumer = GetType().Name,
Type = MetadataType.EpisodeImage,
RelativePath = DiskProviderBase.GetRelativePath(series.Path, image.Path)
};
DownloadImage(series, image.Url, image.Path);
result.Add(metadata);
}
return result;
}
private void DownloadImage(Series series, string url, string path)
{
try
{
_httpProvider.DownloadFile(url, path);
}
catch (WebException e)
{
_logger.Warn(string.Format("Couldn't download image {0} for {1}. {2}", url, series, e.Message));
}
catch (Exception e)
{
_logger.ErrorException("Couldn't download image " + url + " for " + series, e);
}
}
}
}

View File

@ -12,5 +12,6 @@ namespace NzbDrone.Core.Metadata.Files
public DateTime LastUpdated { get; set; }
public Int32? EpisodeFileId { get; set; }
public Int32? SeasonNumber { get; set; }
public String Hash { get; set; }
}
}

View File

@ -13,17 +13,6 @@ namespace NzbDrone.Core.Metadata
{
public abstract class MetadataBase<TSettings> : IMetadata where TSettings : IProviderConfig, new()
{
private readonly IDiskProvider _diskProvider;
private readonly IHttpProvider _httpProvider;
private readonly Logger _logger;
protected MetadataBase(IDiskProvider diskProvider, IHttpProvider httpProvider, Logger logger)
{
_diskProvider = diskProvider;
_httpProvider = httpProvider;
_logger = logger;
}
public Type ConfigContract
{
get
@ -42,11 +31,15 @@ namespace NzbDrone.Core.Metadata
public ProviderDefinition Definition { get; set; }
public abstract void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
public abstract void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload);
public abstract void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
public abstract List<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
public abstract MetadataFile FindMetadataFile(Series series, string path);
public abstract MetadataFileResult SeriesMetadata(Series series);
public abstract MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
public abstract List<ImageFileResult> SeriesImages(Series series);
public abstract List<ImageFileResult> SeasonImages(Series series, Season season);
public abstract List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile);
protected TSettings Settings
{
get
@ -55,28 +48,6 @@ namespace NzbDrone.Core.Metadata
}
}
protected virtual void DownloadImage(Series series, string url, string path)
{
try
{
if (_diskProvider.FileExists(path))
{
_logger.Debug("Image already exists: {0}, will not download again.", path);
return;
}
_httpProvider.DownloadFile(url, path);
}
catch (WebException e)
{
_logger.Warn(string.Format("Couldn't download image {0} for {1}. {2}", url, series, e.Message));
}
catch (Exception e)
{
_logger.ErrorException("Couldn't download image " + url + " for " + series, e);
}
}
public override string ToString()
{
return GetType().Name;

View File

@ -361,6 +361,8 @@
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadata.cs" />
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadataSettings.cs" />
<Compile Include="Metadata\ExistingMetadataService.cs" />
<Compile Include="Metadata\Files\ImageFileResult.cs" />
<Compile Include="Metadata\Files\MetadataFileResult.cs" />
<Compile Include="Metadata\Files\MetadataFilesUpdated.cs" />
<Compile Include="Metadata\Files\MetadataFile.cs" />
<Compile Include="Metadata\Files\MetadataFileRepository.cs" />
@ -539,6 +541,7 @@
<Compile Include="Qualities\QualityProfileItem.cs" />
<Compile Include="Rest\JsonNetSerializer.cs" />
<Compile Include="RootFolders\RootFolderRepository.cs" />
<Compile Include="Security.cs" />
<Compile Include="ThingiProvider\ConfigContractNotFoundException.cs" />
<Compile Include="ThingiProvider\Events\ProviderUpdatedEvent.cs" />
<Compile Include="ThingiProvider\IProvider.cs" />

View File

@ -0,0 +1,26 @@
using System.Security.Cryptography;
using System.Text;
namespace NzbDrone.Core
{
public static class Security
{
public static string SHA256Hash(this string input)
{
var stringBuilder = new StringBuilder();
using (var hash = SHA256Managed.Create())
{
var enc = Encoding.UTF8;
var result = hash.ComputeHash(enc.GetBytes(input));
foreach (var b in result)
{
stringBuilder.Append(b.ToString("x2"));
}
}
return stringBuilder.ToString();
}
}
}