From cbd8e986773edac5025079707d2b8d03c02d9d82 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 18 Feb 2014 20:51:37 -0800 Subject: [PATCH] More xbmc metadata improvements New: Create/update episode metadata when series is refreshed Fixed: Episode Metadata when screenshot is not available Fixed: Episode metadata being stored in database incorrectly Fixed: Do not create metadata when series folder does not exist --- .../044_fix_xbmc_episode_metadata.cs | 27 ++++++ .../MetaData/Consumers/Xbmc/XbmcMetadata.cs | 91 +++++++++++++++---- .../MetaData/Files/CleanMetadataService.cs | 45 +++++++++ src/NzbDrone.Core/MetaData/MetadataService.cs | 10 +- .../Metadata/Files/MetadataFileService.cs | 6 ++ src/NzbDrone.Core/Metadata/MetadataBase.cs | 5 - src/NzbDrone.Core/NzbDrone.Core.csproj | 4 + .../RootFolders/RootFolderLayoutTemplate.html | 1 + 8 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/044_fix_xbmc_episode_metadata.cs create mode 100644 src/NzbDrone.Core/MetaData/Files/CleanMetadataService.cs diff --git a/src/NzbDrone.Core/Datastore/Migration/044_fix_xbmc_episode_metadata.cs b/src/NzbDrone.Core/Datastore/Migration/044_fix_xbmc_episode_metadata.cs new file mode 100644 index 000000000..0c645259b --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/044_fix_xbmc_episode_metadata.cs @@ -0,0 +1,27 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(44)] + public class fix_xbmc_episode_metadata : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + //Convert Episode Metadata to proper type + Execute.Sql("UPDATE MetadataFiles " + + "SET Type = 2 " + + "WHERE Consumer = 'XbmcMetadata' " + + "AND EpisodeFileId IS NOT NULL " + + "AND Type = 4 " + + "AND RelativePath LIKE '%.nfo'"); + + //Convert Episode Images to proper type + Execute.Sql("UPDATE MetadataFiles " + + "SET Type = 5 " + + "WHERE Consumer = 'XbmcMetadata' " + + "AND EpisodeFileId IS NOT NULL " + + "AND Type = 4"); + } + } +} diff --git a/src/NzbDrone.Core/MetaData/Consumers/Xbmc/XbmcMetadata.cs b/src/NzbDrone.Core/MetaData/Consumers/Xbmc/XbmcMetadata.cs index 2579511e7..d95eaf648 100644 --- a/src/NzbDrone.Core/MetaData/Consumers/Xbmc/XbmcMetadata.cs +++ b/src/NzbDrone.Core/MetaData/Consumers/Xbmc/XbmcMetadata.cs @@ -9,6 +9,7 @@ using System.Xml.Linq; using NLog; using NzbDrone.Common; using NzbDrone.Common.Disk; +using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaCover; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Messaging.Events; @@ -25,6 +26,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc private readonly IMetadataFileService _metadataFileService; private readonly IDiskProvider _diskProvider; private readonly IHttpProvider _httpProvider; + private readonly IEpisodeService _episodeService; private readonly Logger _logger; public XbmcMetadata(IEventAggregator eventAggregator, @@ -33,6 +35,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc IMetadataFileService metadataFileService, IDiskProvider diskProvider, IHttpProvider httpProvider, + IEpisodeService episodeService, Logger logger) : base(diskProvider, httpProvider, logger) { @@ -42,6 +45,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc _metadataFileService = metadataFileService; _diskProvider = diskProvider; _httpProvider = httpProvider; + _episodeService = episodeService; _logger = logger; } @@ -51,40 +55,63 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc public override void OnSeriesUpdated(Series series, List existingMetadataFiles) { + if (!_diskProvider.FolderExists(series.Path)) + { + _logger.Info("Series folder does not exist, skipping metadata creation"); + return; + } + if (Settings.SeriesMetadata) { - EnsureFolder(series.Path); WriteTvShowNfo(series, existingMetadataFiles); } if (Settings.SeriesImages) { - EnsureFolder(series.Path); WriteSeriesImages(series, existingMetadataFiles); } if (Settings.SeasonImages) { - EnsureFolder(series.Path); WriteSeasonImages(series, existingMetadataFiles); } + + var episodeFiles = GetEpisodeFiles(series.Id); + + foreach (var episodeFile in episodeFiles) + { + if (Settings.EpisodeMetadata) + { + WriteEpisodeNfo(series, episodeFile, existingMetadataFiles); + } + } + + foreach (var episodeFile in episodeFiles) + { + if (Settings.EpisodeImages) + { + WriteEpisodeImages(series, episodeFile, existingMetadataFiles); + } + } } public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload) { if (Settings.EpisodeMetadata) { - WriteEpisodeNfo(series, episodeFile); + WriteEpisodeNfo(series, episodeFile, new List()); } if (Settings.EpisodeImages) { - WriteEpisodeImages(series, episodeFile); + WriteEpisodeImages(series, episodeFile, new List()); } } public override void AfterRename(Series series) { + //TODO: This should be part of the base class, but could be overwritten if the logic needs to be different + //or it could be done in MetadataService instead of having each metadata consumer do it var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id); var episodeFilesMetadata = _metadataFileService.GetFilesBySeries(series.Id).Where(c => c.EpisodeFileId > 0).ToList(); @@ -305,7 +332,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc } } - private void WriteEpisodeNfo(Series series, EpisodeFile episodeFile) + private void WriteEpisodeNfo(Series series, EpisodeFile episodeFile, List existingMetadataFiles) { var filename = episodeFile.Path.Replace(Path.GetExtension(episodeFile.Path), ".nfo"); @@ -322,6 +349,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc using (var xw = XmlWriter.Create(sb, xws)) { var doc = new XDocument(); + var image = episode.Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot); var details = new XElement("episodedetails"); details.Add(new XElement("title", episode.Title)); @@ -333,8 +361,17 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc //If trakt ever gets airs before information for specials we should add set it details.Add(new XElement("displayseason")); details.Add(new XElement("displayepisode")); + + if (image == null) + { + details.Add(new XElement("thumb")); + } + + else + { + details.Add(new XElement("thumb", image.Url)); + } - details.Add(new XElement("thumb", episode.Images.Single(i => i.CoverType == MediaCoverTypes.Screenshot).Url)); details.Add(new XElement("watched", "false")); details.Add(new XElement("rating", episode.Ratings.Percentage)); @@ -353,19 +390,21 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc _logger.Debug("Saving episodedetails to: {0}", filename); _diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray())); - var metadata = new MetadataFile - { - SeriesId = series.Id, - EpisodeFileId = episodeFile.Id, - Consumer = GetType().Name, - Type = MetadataType.SeasonImage, - RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename) - }; + var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata && + c.EpisodeFileId == episodeFile.Id) ?? + new MetadataFile + { + SeriesId = series.Id, + EpisodeFileId = episodeFile.Id, + Consumer = GetType().Name, + Type = MetadataType.EpisodeMetadata, + RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename) + }; _eventAggregator.PublishEvent(new MetadataFileUpdated(metadata)); } - private void WriteEpisodeImages(Series series, EpisodeFile episodeFile) + private void WriteEpisodeImages(Series series, EpisodeFile episodeFile, List existingMetadataFiles) { var screenshot = episodeFile.Episodes.Value.First().Images.Single(i => i.CoverType == MediaCoverTypes.Screenshot); @@ -373,16 +412,32 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc DownloadImage(series, screenshot.Url, filename); - var metadata = new MetadataFile + var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage && + c.EpisodeFileId == episodeFile.Id) ?? + new MetadataFile { SeriesId = series.Id, EpisodeFileId = episodeFile.Id, Consumer = GetType().Name, - Type = MetadataType.SeasonImage, + Type = MetadataType.EpisodeImage, RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename) }; _eventAggregator.PublishEvent(new MetadataFileUpdated(metadata)); } + + private List GetEpisodeFiles(int seriesId) + { + var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId); + var episodes = _episodeService.GetEpisodeBySeries(seriesId); + + foreach (var episodeFile in episodeFiles) + { + var localEpisodeFile = episodeFile; + episodeFile.Episodes = new LazyList(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id)); + } + + return episodeFiles; + } } } diff --git a/src/NzbDrone.Core/MetaData/Files/CleanMetadataService.cs b/src/NzbDrone.Core/MetaData/Files/CleanMetadataService.cs new file mode 100644 index 000000000..cdcbb0088 --- /dev/null +++ b/src/NzbDrone.Core/MetaData/Files/CleanMetadataService.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.IO; +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Metadata.Files +{ + public interface ICleanMetadataService + { + void Clean(Series series); + } + + public class CleanMetadataService : ICleanMetadataService + { + private readonly IMetadataFileService _metadataFileService; + private readonly IDiskProvider _diskProvider; + private readonly Logger _logger; + + public CleanMetadataService(IMetadataFileService metadataFileService, + IDiskProvider diskProvider, + Logger logger) + { + _metadataFileService = metadataFileService; + _diskProvider = diskProvider; + _logger = logger; + } + + public void Clean(Series series) + { + _logger.Trace("Cleaning missing metadata files for series: {0}", series.Title); + + var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id); + + foreach (var metadataFile in metadataFiles) + { + if (!_diskProvider.FileExists(Path.Combine(series.Path, metadataFile.RelativePath))) + { + _logger.Trace("Deleting metadata file from database: {0}", metadataFile.RelativePath); + _metadataFileService.Delete(metadataFile.Id); + } + } + } + } +} diff --git a/src/NzbDrone.Core/MetaData/MetadataService.cs b/src/NzbDrone.Core/MetaData/MetadataService.cs index 461b992d2..008354c75 100644 --- a/src/NzbDrone.Core/MetaData/MetadataService.cs +++ b/src/NzbDrone.Core/MetaData/MetadataService.cs @@ -7,24 +7,30 @@ using NzbDrone.Core.Metadata.Files; namespace NzbDrone.Core.Metadata { - public class NotificationService + public class MetadataService : IHandle, IHandle, IHandle { private readonly IMetadataFactory _metadataFactory; private readonly IMetadataFileService _metadataFileService; + private readonly ICleanMetadataService _cleanMetadataService; private readonly Logger _logger; - public NotificationService(IMetadataFactory metadataFactory, IMetadataFileService metadataFileService, Logger logger) + public MetadataService(IMetadataFactory metadataFactory, + IMetadataFileService metadataFileService, + ICleanMetadataService cleanMetadataService, + Logger logger) { _metadataFactory = metadataFactory; _metadataFileService = metadataFileService; + _cleanMetadataService = cleanMetadataService; _logger = logger; } public void Handle(MediaCoversUpdatedEvent message) { + _cleanMetadataService.Clean(message.Series); var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id); foreach (var consumer in _metadataFactory.Enabled()) diff --git a/src/NzbDrone.Core/Metadata/Files/MetadataFileService.cs b/src/NzbDrone.Core/Metadata/Files/MetadataFileService.cs index f888bfdb8..56471a0b1 100644 --- a/src/NzbDrone.Core/Metadata/Files/MetadataFileService.cs +++ b/src/NzbDrone.Core/Metadata/Files/MetadataFileService.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Core.Metadata.Files MetadataFile FindByPath(string path); List FilterExistingFiles(List files, Series series); MetadataFile Upsert(MetadataFile metadataFile); + void Delete(int id); } public class MetadataFileService : IMetadataFileService, @@ -72,6 +73,11 @@ namespace NzbDrone.Core.Metadata.Files return _repository.Upsert(metadataFile); } + public void Delete(int id) + { + _repository.Delete(id); + } + public void HandleAsync(SeriesDeletedEvent message) { _logger.Trace("Deleting Metadata from database for series: {0}", message.Series); diff --git a/src/NzbDrone.Core/Metadata/MetadataBase.cs b/src/NzbDrone.Core/Metadata/MetadataBase.cs index dbd613f1d..29143a424 100644 --- a/src/NzbDrone.Core/Metadata/MetadataBase.cs +++ b/src/NzbDrone.Core/Metadata/MetadataBase.cs @@ -55,11 +55,6 @@ namespace NzbDrone.Core.Metadata } } - protected virtual void EnsureFolder(string path) - { - _diskProvider.CreateFolder(path); - } - protected virtual void DownloadImage(Series series, string url, string path) { try diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index a87eaeb6c..6de48e0c3 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -200,6 +200,9 @@ + + Code + @@ -334,6 +337,7 @@ + diff --git a/src/UI/AddSeries/RootFolders/RootFolderLayoutTemplate.html b/src/UI/AddSeries/RootFolders/RootFolderLayoutTemplate.html index 390b3c056..c8ba616c5 100644 --- a/src/UI/AddSeries/RootFolders/RootFolderLayoutTemplate.html +++ b/src/UI/AddSeries/RootFolders/RootFolderLayoutTemplate.html @@ -4,6 +4,7 @@