diff --git a/src/NzbDrone.Common/IEnumerableExtensions.cs b/src/NzbDrone.Common/IEnumerableExtensions.cs index 11626292a..e4d3f5bfe 100644 --- a/src/NzbDrone.Common/IEnumerableExtensions.cs +++ b/src/NzbDrone.Common/IEnumerableExtensions.cs @@ -12,5 +12,15 @@ namespace NzbDrone.Common return source.Where(element => knownKeys.Add(keySelector(element))); } + + public static void AddIfNotNull(this List source, TSource item) + { + if (item == null) + { + return; + } + + source.Add(item); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MetaData/Consumers/Roksbox/RoksboxMetadata.cs b/src/NzbDrone.Core/MetaData/Consumers/Roksbox/RoksboxMetadata.cs index b6b396d69..cb4c98a57 100644 --- a/src/NzbDrone.Core/MetaData/Consumers/Roksbox/RoksboxMetadata.cs +++ b/src/NzbDrone.Core/MetaData/Consumers/Roksbox/RoksboxMetadata.cs @@ -21,117 +21,23 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox { public class RoksboxMetadata : MetadataBase { - 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 ValidCertification = new List { "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 (?\d+))|(?specials)", RegexOptions.Compiled | RegexOptions.IgnoreCase); - public override void OnSeriesUpdated(Series series, List existingMetadataFiles, List episodeFiles) - { - var metadataFiles = new List(); - - 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(); - - if (Settings.EpisodeMetadata) - { - metadataFiles.Add(WriteEpisodeMetadata(series, episodeFile, new List())); - } - - if (Settings.EpisodeImages) - { - var metadataFile = WriteEpisodeImages(series, episodeFile, new List()); - - if (metadataFile != null) - { - metadataFiles.Add(metadataFile); - } - } - - _eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles)); - } - - public override void AfterRename(Series series, List existingMetadataFiles, List episodeFiles) + public override List AfterRename(Series series, List existingMetadataFiles, List episodeFiles) { var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList(); var updatedMetadataFiles = new List(); @@ -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,137 +143,20 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox return null; } - private MetadataFile WriteSeriesImages(Series series, List 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) - { - _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, 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; - } - } - - private IEnumerable WriteSeasonImages(Series series, List existingMetadataFiles) - { - _logger.Debug("Writing season images for {0}.", series.Title); - //Create a dictionary between season number and output folder - var seasonFolderMap = new Dictionary(); - 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; - } - - //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; - } + //Series metadata is not supported + return null; } - private MetadataFile WriteEpisodeMetadata(Series series, EpisodeFile episodeFile, List existingMetadataFiles) + public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile) { - 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) + if (!Settings.EpisodeMetadata) { - var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath); - if (!filename.PathEquals(fullPath)) - { - _diskProvider.MoveFile(fullPath, filename); - existingMetadata.RelativePath = relativePath; - } + return null; } - _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) @@ -389,7 +178,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox 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" ) ); + details.Add(new XElement("mpaa", ValidCertification.Contains(series.Certification.ToUpperInvariant()) ? series.Certification.ToUpperInvariant() : "UNRATED")); doc.Add(details); doc.Save(xw); @@ -397,62 +186,61 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox 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(GetEpisodeMetadataFilename(episodeFile.Path), xmlResult.Trim(Environment.NewLine.ToCharArray())); } - private MetadataFile WriteEpisodeImages(Series series, EpisodeFile episodeFile, List existingMetadataFiles) + public override List SeriesImages(Series series) { - var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot); - - if (screenshot == null) + var image = series.Images.SingleOrDefault(c => c.CoverType == MediaCoverTypes.Poster) ?? series.Images.FirstOrDefault(); + if (image == null) { - _logger.Trace("Episode screenshot not available"); + _logger.Trace("Failed to find suitable Series image for series {0}.", series.Title); return null; } - var filename = GetEpisodeImageFilename(episodeFile.Path); - var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename); + var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType); + var destination = Path.Combine(series.Path, Path.GetFileName(series.Path) + Path.GetExtension(source)); - var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage && - c.EpisodeFileId == episodeFile.Id); + return new List{ new ImageFileResult(destination, source) }; + } + + public override List SeasonImages(Series series, Season season) + { + var seasonFolders = GetSeasonFolders(series); - if (existingMetadata != null) + string seasonFolder; + if (!seasonFolders.TryGetValue(season.SeasonNumber, out seasonFolder)) { - var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath); - if (!filename.PathEquals(fullPath)) - { - _diskProvider.MoveFile(fullPath, filename); - existingMetadata.RelativePath = relativePath; - } + _logger.Trace("Failed to find season folder for series {0}, season {1}.", series.Title, season.SeasonNumber); + return new List(); } - DownloadImage(series, screenshot.Url, filename); + //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(); + } - var metadata = existingMetadata ?? - new MetadataFile - { - SeriesId = series.Id, - EpisodeFileId = episodeFile.Id, - Consumer = GetType().Name, - Type = MetadataType.EpisodeImage, - RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename) - }; + var filename = Path.GetFileName(seasonFolder) + ".jpg"; + var path = Path.Combine(series.Path, seasonFolder, filename); + + return new List { new ImageFileResult(path, image.Url) }; + } + + public override List EpisodeImages(Series series, EpisodeFile episodeFile) + { + var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot); - return metadata; + if (screenshot == null) + { + _logger.Trace("Episode screenshot not available"); + return new List(); + } + + return new List {new ImageFileResult(GetEpisodeImageFilename(episodeFile.Path), screenshot.Url)}; } private string GetEpisodeMetadataFilename(string episodeFilePath) @@ -464,5 +252,44 @@ namespace NzbDrone.Core.Metadata.Consumers.Roksbox { return Path.ChangeExtension(episodeFilePath, "jpg"); } + + private Dictionary GetSeasonFolders(Series series) + { + var seasonFolderMap = new Dictionary(); + + 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; + } } } diff --git a/src/NzbDrone.Core/MetaData/Consumers/Wdtv/WdtvMetadata.cs b/src/NzbDrone.Core/MetaData/Consumers/Wdtv/WdtvMetadata.cs index e76a341bc..37ca71d0f 100644 --- a/src/NzbDrone.Core/MetaData/Consumers/Wdtv/WdtvMetadata.cs +++ b/src/NzbDrone.Core/MetaData/Consumers/Wdtv/WdtvMetadata.cs @@ -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 { - 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 (?\d+))|(?specials)", RegexOptions.Compiled | RegexOptions.IgnoreCase); - public override void OnSeriesUpdated(Series series, List existingMetadataFiles, List episodeFiles) - { - var metadataFiles = new List(); - - 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(); - - if (Settings.EpisodeMetadata) - { - metadataFiles.Add(WriteEpisodeMetadata(series, episodeFile, new List())); - } - - if (Settings.EpisodeImages) - { - var metadataFile = WriteEpisodeImages(series, episodeFile, new List()); - - if (metadataFile != null) - { - metadataFiles.Add(metadataFile); - } - } - - _eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles)); - } - - public override void AfterRename(Series series, List existingMetadataFiles, List episodeFiles) + public override List AfterRename(Series series, List existingMetadataFiles, List episodeFiles) { var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList(); var updatedMetadataFiles = new List(); @@ -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 existingMetadataFiles) - { - //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 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 WriteSeasonImages(Series series, List existingMetadataFiles) + public override MetadataFileResult SeriesMetadata(Series series) { - _logger.Debug("Writing season images for {0}.", series.Title); - //Create a dictionary between season number and output folder - var seasonFolderMap = new Dictionary(); - 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; - } + //Series metadata is not supported + return null; } - private MetadataFile WriteEpisodeMetadata(Series series, EpisodeFile episodeFile, List existingMetadataFiles) + public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile) { - 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) + if (!Settings.EpisodeMetadata) { - var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath); - if (!filename.PathEquals(fullPath)) - { - _diskProvider.MoveFile(fullPath, filename); - existingMetadata.RelativePath = relativePath; - } + return null; } - _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 existingMetadataFiles) + public override List SeriesImages(Series series) { - var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot); + if (!Settings.SeriesImages) + { + return new List(); + } - if (screenshot == null) + //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("Episode screenshot not available"); - return null; + _logger.Trace("Failed to find suitable Series image for series {0}.", series.Title); + return new List(); } - var filename = GetEpisodeImageFilename(episodeFile.Path); - var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename); + var source = _mediaCoverService.GetCoverPath(series.Id, image.CoverType); + var destination = Path.Combine(series.Path, "folder" + Path.GetExtension(source)); - var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage && - c.EpisodeFileId == episodeFile.Id); + return new List + { + new ImageFileResult(destination, source) + }; + } - if (existingMetadata != null) + public override List SeasonImages(Series series, Season season) + { + if (!Settings.SeasonImages) { - var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath); - if (!filename.PathEquals(fullPath)) - { - _diskProvider.MoveFile(fullPath, filename); - existingMetadata.RelativePath = relativePath; - } + return new List(); } + + var seasonFolders = GetSeasonFolders(series); - DownloadImage(series, screenshot.Url, filename); + //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(); + } - var metadata = existingMetadata ?? - new MetadataFile - { - SeriesId = series.Id, - EpisodeFileId = episodeFile.Id, - Consumer = GetType().Name, - Type = MetadataType.EpisodeImage, - RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename) - }; + //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(); + } + + var path = Path.Combine(series.Path, seasonFolder, "folder.jpg"); - return metadata; + return new List{ new ImageFileResult(path, image.Url) }; + } + + public override List EpisodeImages(Series series, EpisodeFile episodeFile) + { + if (!Settings.EpisodeImages) + { + return new List(); + } + + 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(); + } + + return new List{ 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 GetSeasonFolders(Series series) + { + var seasonFolderMap = new Dictionary(); + + 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; + } } } diff --git a/src/NzbDrone.Core/MetaData/Consumers/Xbmc/XbmcMetadata.cs b/src/NzbDrone.Core/MetaData/Consumers/Xbmc/XbmcMetadata.cs index 4089c6863..806843df9 100644 --- a/src/NzbDrone.Core/MetaData/Consumers/Xbmc/XbmcMetadata.cs +++ b/src/NzbDrone.Core/MetaData/Consumers/Xbmc/XbmcMetadata.cs @@ -19,32 +19,16 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc { public class XbmcMetadata : MetadataBase { - 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(?\d{2,}|-all|-specials)-(?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 existingMetadataFiles, List episodeFiles) - { - var metadataFiles = new List(); - - 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(); - - if (Settings.EpisodeMetadata) - { - metadataFiles.Add(WriteEpisodeNfo(series, episodeFile, new List())); - } - - if (Settings.EpisodeImages) - { - var metadataFile = WriteEpisodeImages(series, episodeFile, new List()); - - if (metadataFile != null) - { - metadataFiles.Add(metadataFile); - } - WriteEpisodeImages(series, episodeFile, new List()); - } - - _eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles)); - } - - public override void AfterRename(Series series, List existingMetadataFiles, List episodeFiles) + public override List AfterRename(Series series, List existingMetadataFiles, List episodeFiles) { var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList(); var updatedMetadataFiles = new List(); @@ -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 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; - } - } - - private IEnumerable WriteSeriesImages(Series series, List 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; + return new MetadataFileResult(Path.Combine(series.Path, "tvshow.nfo"), doc.ToString()); } } - private IEnumerable WriteSeasonImages(Series series, List 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 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 new MetadataFileResult(GetEpisodeNfoFilename(episodeFile.Path), xmlResult.Trim(Environment.NewLine.ToCharArray())); + } - return metadata; + public override List SeriesImages(Series series) + { + if (!Settings.SeriesImages) + { + return new List(); + } + + return ProcessSeriesImages(series).ToList(); } - private MetadataFile WriteEpisodeImages(Series series, EpisodeFile episodeFile, List existingMetadataFiles) + public override List SeasonImages(Series series, Season season) { + if (!Settings.SeasonImages) + { + return new List(); + } + + return ProcessSeasonImages(series, season).ToList(); + } + + public override List EpisodeImages(Series series, EpisodeFile episodeFile) + { + if (!Settings.EpisodeImages) + { + return new List(); + } + 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 + { + new ImageFileResult(GetEpisodeImageFilename(episodeFile.Path), screenshot.Url) + }; + } + + private IEnumerable ProcessSeriesImages(Series series) + { + 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)); - var existingMetadata = existingMetadataFiles.FirstOrDefault(c => c.Type == MetadataType.EpisodeImage && - c.EpisodeFileId == episodeFile.Id); + yield return new ImageFileResult(destination, source); + } + } - if (existingMetadata != null) + private IEnumerable ProcessSeasonImages(Series series, Season season) + { + foreach (var image in season.Images) { - var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath); - if (!filename.PathEquals(fullPath)) + var filename = String.Format("season{0:00}-{1}.jpg", season.SeasonNumber, image.CoverType.ToString().ToLower()); + + if (season.SeasonNumber == 0) { - _diskProvider.MoveFile(fullPath, filename); - existingMetadata.RelativePath = relativePath; + filename = String.Format("season-specials-{0}.jpg", image.CoverType.ToString().ToLower()); } - } - DownloadImage(series, screenshot.Url, filename); + var path = Path.Combine(series.Path, 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; + yield return new ImageFileResult(Path.Combine(series.Path, filename), image.Url); + } } private string GetEpisodeNfoFilename(string episodeFilePath) diff --git a/src/NzbDrone.Core/MetaData/Files/ImageFileResult.cs b/src/NzbDrone.Core/MetaData/Files/ImageFileResult.cs new file mode 100644 index 000000000..e0330be8a --- /dev/null +++ b/src/NzbDrone.Core/MetaData/Files/ImageFileResult.cs @@ -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; + } + } +} diff --git a/src/NzbDrone.Core/MetaData/Files/MetadataFileResult.cs b/src/NzbDrone.Core/MetaData/Files/MetadataFileResult.cs new file mode 100644 index 000000000..1152bd487 --- /dev/null +++ b/src/NzbDrone.Core/MetaData/Files/MetadataFileResult.cs @@ -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; + } + } +} diff --git a/src/NzbDrone.Core/MetaData/IMetadata.cs b/src/NzbDrone.Core/MetaData/IMetadata.cs index 63fa19d73..f9c1feae3 100644 --- a/src/NzbDrone.Core/MetaData/IMetadata.cs +++ b/src/NzbDrone.Core/MetaData/IMetadata.cs @@ -8,9 +8,14 @@ namespace NzbDrone.Core.Metadata { public interface IMetadata : IProvider { - void OnSeriesUpdated(Series series, List existingMetadataFiles, List episodeFiles); - void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload); - void AfterRename(Series series, List existingMetadataFiles, List episodeFiles); + List AfterRename(Series series, List existingMetadataFiles, List episodeFiles); MetadataFile FindMetadataFile(Series series, string path); + + MetadataFileResult SeriesMetadata(Series series); + MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile); + List SeriesImages(Series series); + List SeasonImages(Series series, Season season); + List EpisodeImages(Series series, EpisodeFile episodeFile); + } } diff --git a/src/NzbDrone.Core/MetaData/MetadataService.cs b/src/NzbDrone.Core/MetaData/MetadataService.cs index 681eed8d0..ff44e044d 100644 --- a/src/NzbDrone.Core/MetaData/MetadataService.cs +++ b/src/NzbDrone.Core/MetaData/MetadataService.cs @@ -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(); + + 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(); + + files.AddIfNotNull(ProcessEpisodeMetadata(consumer, message.EpisodeInfo.Series, message.ImportedEpisode, new List())); + files.AddRange(ProcessEpisodeImages(consumer, message.EpisodeInfo.Series, message.ImportedEpisode, new List())); + + _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 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 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 ProcessSeriesImages(IMetadata consumer, Series series, List existingMetadataFiles) + { + var result = new List(); + + 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 ProcessSeasonImages(IMetadata consumer, Series series, List existingMetadataFiles) + { + var result = new List(); + + 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 ProcessEpisodeImages(IMetadata consumer, Series series, EpisodeFile episodeFile, List existingMetadataFiles) + { + var result = new List(); + + 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{ 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); + } + } } } diff --git a/src/NzbDrone.Core/Metadata/Files/MetadataFile.cs b/src/NzbDrone.Core/Metadata/Files/MetadataFile.cs index df52e2bc4..c30e9064d 100644 --- a/src/NzbDrone.Core/Metadata/Files/MetadataFile.cs +++ b/src/NzbDrone.Core/Metadata/Files/MetadataFile.cs @@ -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; } } } diff --git a/src/NzbDrone.Core/Metadata/MetadataBase.cs b/src/NzbDrone.Core/Metadata/MetadataBase.cs index 3bebd9d90..d674f2873 100644 --- a/src/NzbDrone.Core/Metadata/MetadataBase.cs +++ b/src/NzbDrone.Core/Metadata/MetadataBase.cs @@ -13,17 +13,6 @@ namespace NzbDrone.Core.Metadata { public abstract class MetadataBase : 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 existingMetadataFiles, List episodeFiles); - public abstract void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload); - public abstract void AfterRename(Series series, List existingMetadataFiles, List episodeFiles); + public abstract List AfterRename(Series series, List existingMetadataFiles, List 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 SeriesImages(Series series); + public abstract List SeasonImages(Series series, Season season); + public abstract List 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; diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index b8938f15e..76f4cb5bb 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -361,6 +361,8 @@ + + @@ -539,6 +541,7 @@ + diff --git a/src/NzbDrone.Core/Security.cs b/src/NzbDrone.Core/Security.cs new file mode 100644 index 000000000..abc950809 --- /dev/null +++ b/src/NzbDrone.Core/Security.cs @@ -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(); + } + } +}