From d6cf7b51acda145d32c9944f1b66728c7e09a9f8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 23 Jun 2013 13:48:30 -0400 Subject: [PATCH] added movie, series, folder and episode xml saving support --- MediaBrowser.Controller/Entities/BaseItem.cs | 8 +- .../Library/ILibraryManager.cs | 5 +- .../Library/IMetadataSaver.cs | 3 +- .../Providers/BaseItemXmlParser.cs | 37 +- .../Providers/IProviderManager.cs | 19 +- .../MediaBrowser.Providers.csproj | 5 +- .../Movies/MovieDbProvider.cs | 55 --- .../Movies/MovieProviderFromJson.cs | 122 ------ .../Movies/MovieProviderFromXml.cs | 3 + .../Savers/EpisodeXmlSaver.cs | 93 +++++ .../Savers/FolderXmlSaver.cs | 60 +++ .../Savers/MovieXmlSaver.cs | 22 +- .../Savers/SeriesXmlSaver.cs | 103 ++++++ MediaBrowser.Providers/Savers/XmlHelpers.cs | 346 ++++++++++++++++++ .../TV/EpisodeProviderFromXml.cs | 3 + .../TV/SeriesProviderFromXml.cs | 6 +- .../Library/LibraryManager.cs | 45 +-- .../Providers/ProviderManager.cs | 43 ++- .../ApplicationHost.cs | 8 +- 19 files changed, 740 insertions(+), 246 deletions(-) delete mode 100644 MediaBrowser.Providers/Movies/MovieProviderFromJson.cs create mode 100644 MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs create mode 100644 MediaBrowser.Providers/Savers/FolderXmlSaver.cs create mode 100644 MediaBrowser.Providers/Savers/SeriesXmlSaver.cs create mode 100644 MediaBrowser.Providers/Savers/XmlHelpers.cs diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 00ab867b2d..e72ffdb0b1 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -314,7 +314,7 @@ namespace MediaBrowser.Controller.Entities { throw new IOException("Unable to retrieve file system info for " + path); } - + var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths) { FileInfo = pathInfo, @@ -464,6 +464,12 @@ namespace MediaBrowser.Controller.Entities /// The official rating. public virtual string OfficialRating { get; set; } + /// + /// Gets or sets the official rating description. + /// + /// The official rating description. + public string OfficialRatingDescription { get; set; } + /// /// Gets or sets the custom rating. /// diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 6d6fab3bea..46a6d38df1 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -152,16 +152,13 @@ namespace MediaBrowser.Controller.Library /// The item comparers. /// The prescan tasks. /// The postscan tasks. - /// The savers. - /// The ?. void AddParts(IEnumerable rules, IEnumerable pluginFolders, IEnumerable resolvers, IEnumerable introProviders, IEnumerable itemComparers, IEnumerable prescanTasks, - IEnumerable postscanTasks, - IEnumerable savers); + IEnumerable postscanTasks); /// /// Sorts the specified items. diff --git a/MediaBrowser.Controller/Library/IMetadataSaver.cs b/MediaBrowser.Controller/Library/IMetadataSaver.cs index 419b65f6fa..86e2738af4 100644 --- a/MediaBrowser.Controller/Library/IMetadataSaver.cs +++ b/MediaBrowser.Controller/Library/IMetadataSaver.cs @@ -1,6 +1,5 @@ using MediaBrowser.Controller.Entities; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Controller.Library { @@ -29,6 +28,6 @@ namespace MediaBrowser.Controller.Library /// The item. /// The cancellation token. /// Task. - Task Save(BaseItem item, CancellationToken cancellationToken); + void Save(BaseItem item, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 6459874375..d06c89a010 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.Controller.Providers } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - + /// /// Fetches metadata from one Xml Element /// @@ -205,6 +205,17 @@ namespace MediaBrowser.Controller.Providers break; } + case "MPAADescription": + { + var rating = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(rating)) + { + item.OfficialRatingDescription = rating; + } + break; + } + case "CustomRating": { var val = reader.ReadElementContentAsString(); @@ -306,7 +317,7 @@ namespace MediaBrowser.Controller.Providers { var actors = reader.ReadInnerXml(); - + if (actors.Contains("<")) { // This is one of the mis-named "Actors" full nodes created by MB2 @@ -378,7 +389,7 @@ namespace MediaBrowser.Controller.Providers { float val; // All external meta is saving this as '.' for decimal I believe...but just to be sure - if (float.TryParse(rating.Replace(',','.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out val)) + if (float.TryParse(rating.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out val)) { item.CommunityRating = val; } @@ -412,6 +423,14 @@ namespace MediaBrowser.Controller.Providers } break; + case "CollectionNumber": + var tmdbCollection = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(tmdbCollection)) + { + item.SetProviderId(MetadataProviders.TmdbCollection, tmdbCollection); + } + break; + case "TVcomId": var TVcomId = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(TVcomId)) @@ -602,8 +621,8 @@ namespace MediaBrowser.Controller.Providers { switch (reader.Name) { - // Removed support for "Value" tag as it conflicted with MPAA rating but leaving this function for possible - // future support of "Description" -ebr + // Removed support for "Value" tag as it conflicted with MPAA rating but leaving this function for possible + // future support of "Description" -ebr default: reader.Skip(); @@ -679,7 +698,7 @@ namespace MediaBrowser.Controller.Providers // Only split by comma if there is no pipe in the string // We have to be careful to not split names like Matthew, Jr. - var separator = value.IndexOf('|') == -1 ? ',' : '|'; + var separator = value.IndexOf('|') == -1 && value.IndexOf(';') == -1 ? new[] { ',' } : new[] { '|', ';' }; value = value.Trim().Trim(separator); @@ -690,12 +709,12 @@ namespace MediaBrowser.Controller.Providers /// Provides an additional overload for string.split /// /// The val. - /// The separator. + /// The separators. /// The options. /// System.String[][]. - private static string[] Split(string val, char separator, StringSplitOptions options) + private static string[] Split(string val, char[] separators, StringSplitOptions options) { - return val.Split(new[] { separator }, options); + return val.Split(separators, options); } } diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 99860bac0d..7f80973e9f 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using System.Collections.Generic; using System.IO; using System.Threading; @@ -6,6 +7,9 @@ using System.Threading.Tasks; namespace MediaBrowser.Controller.Providers { + /// + /// Interface IProviderManager + /// public interface IProviderManager { /// @@ -21,8 +25,17 @@ namespace MediaBrowser.Controller.Providers /// item Task DownloadAndSaveImage(BaseItem item, string source, string targetName, bool saveLocally, SemaphoreSlim resourcePool, CancellationToken cancellationToken); + /// + /// Saves the image. + /// + /// The item. + /// The source. + /// Name of the target. + /// if set to true [save locally]. + /// The cancellation token. + /// Task{System.String}. Task SaveImage(BaseItem item, Stream source, string targetName, bool saveLocally, CancellationToken cancellationToken); - + /// /// Saves to library filesystem. /// @@ -48,7 +61,9 @@ namespace MediaBrowser.Controller.Providers /// Adds the metadata providers. /// /// The providers. - void AddMetadataProviders(IEnumerable providers); + /// The savers. + void AddParts(IEnumerable providers, + IEnumerable savers); /// /// Gets the save path. diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 74397a4588..06c9215210 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -60,7 +60,6 @@ - @@ -77,7 +76,11 @@ + + + + diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 6e42963d0e..10234a0940 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -180,40 +180,6 @@ namespace MediaBrowser.Providers.Movies } } - /// - /// The json provider - /// - protected MovieProviderFromJson JsonProvider; - - /// - /// Sets the persisted last refresh date on the item for this provider. - /// - /// The item. - /// The value. - /// The provider version. - /// The status. - public override void SetLastRefreshed(BaseItem item, DateTime value, string providerVersion, ProviderRefreshStatus status = ProviderRefreshStatus.Success) - { - base.SetLastRefreshed(item, value, providerVersion, status); - - if (ConfigurationManager.Configuration.SaveLocalMeta && item.LocationType == LocationType.FileSystem) - { - //in addition to ours, we need to set the last refreshed time for the local data provider - //so it won't see the new files we download and process them all over again - if (JsonProvider == null) JsonProvider = new MovieProviderFromJson(LogManager, ConfigurationManager, JsonSerializer, HttpClient, ProviderManager); - - BaseProviderInfo data; - - if (!item.ProviderData.TryGetValue(JsonProvider.Id, out data)) - { - data = new BaseProviderInfo(); - } - - data.LastRefreshed = value; - item.ProviderData[JsonProvider.Id] = data; - } - } - private const string TmdbConfigUrl = "http://api.themoviedb.org/3/configuration?api_key={0}"; private const string Search3 = @"http://api.themoviedb.org/3/search/movie?api_key={1}&query={0}&language={2}"; private const string AltTitleSearch = @"http://api.themoviedb.org/3/movie/{0}/alternative_titles?api_key={1}&country={2}"; @@ -228,7 +194,6 @@ namespace MediaBrowser.Providers.Movies new Regex(@"(?.*)") // last resort matches the whole string as the name }; - public const string LocalMetaFileName = "tmdb3.json"; public const string AltMetaFileName = "movie.xml"; protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) @@ -248,13 +213,6 @@ namespace MediaBrowser.Providers.Movies /// Task{System.Boolean}. public override async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - if (HasAltMeta(item)) - { - Logger.Info("MovieDbProvider - Not fetching because 3rd party meta exists for " + item.Name); - SetLastRefreshed(item, DateTime.UtcNow); - return true; - } - cancellationToken.ThrowIfCancellationRequested(); await FetchMovieData(item, cancellationToken).ConfigureAwait(false); @@ -638,19 +596,6 @@ namespace MediaBrowser.Providers.Movies if (mainResult == null) return; ProcessMainInfo(item, mainResult); - - cancellationToken.ThrowIfCancellationRequested(); - - //and save locally - if (ConfigurationManager.Configuration.SaveLocalMeta && item.LocationType == LocationType.FileSystem) - { - var ms = new MemoryStream(); - JsonSerializer.SerializeToStream(mainResult, ms); - - cancellationToken.ThrowIfCancellationRequested(); - - await ProviderManager.SaveToLibraryFilesystem(item, Path.Combine(item.MetaLocation, LocalMetaFileName), ms, cancellationToken).ConfigureAwait(false); - } } /// diff --git a/MediaBrowser.Providers/Movies/MovieProviderFromJson.cs b/MediaBrowser.Providers/Movies/MovieProviderFromJson.cs deleted file mode 100644 index 529122f40a..0000000000 --- a/MediaBrowser.Providers/Movies/MovieProviderFromJson.cs +++ /dev/null @@ -1,122 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Movies -{ - /// - /// Class MovieProviderFromJson - /// - public class MovieProviderFromJson : MovieDbProvider - { - public MovieProviderFromJson(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, IProviderManager providerManager) - : base(logManager, configurationManager, jsonSerializer, httpClient, providerManager) - { - } - - public override bool Supports(BaseItem item) - { - if (item.LocationType != LocationType.FileSystem) - { - return false; - } - - var trailer = item as Trailer; - - if (trailer != null) - { - return !trailer.IsLocalTrailer; - } - - return item is Movie || item is BoxSet || item is MusicVideo; - } - - /// - /// Gets the priority. - /// - /// The priority. - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.Second; } - } - - /// - /// Gets a value indicating whether [requires internet]. - /// - /// true if [requires internet]; otherwise, false. - public override bool RequiresInternet - { - get { return false; } - } - - /// - /// Override this to return the date that should be compared to the last refresh date - /// to determine if this provider should be re-fetched. - /// - /// The item. - /// DateTime. - protected override DateTime CompareDate(BaseItem item) - { - var entry = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, LocalMetaFileName)); - return entry != null ? entry.LastWriteTimeUtc : DateTime.MinValue; - } - - /// - /// Needses the refresh internal. - /// - /// The item. - /// The provider info. - /// true if XXXX, false otherwise - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - if (item.ResolveArgs.ContainsMetaFileByName(AltMetaFileName)) - { - return false; // don't read our file if 3rd party data exists - } - - if (!item.ResolveArgs.ContainsMetaFileByName(LocalMetaFileName)) - { - return false; // nothing to read - } - - // Need to re-override to jump over intermediate implementation - return CompareDate(item) > providerInfo.LastRefreshed; - } - - /// - /// Fetches the async. - /// - /// The item. - /// if set to true [force]. - /// The cancellation token. - /// Task{System.Boolean}. - public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var entry = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, LocalMetaFileName)); - if (entry != null) - { - // read in our saved meta and pass to processing function - var movieData = JsonSerializer.DeserializeFromFile(entry.FullName); - - cancellationToken.ThrowIfCancellationRequested(); - - ProcessMainInfo(item, movieData); - - SetLastRefreshed(item, DateTime.UtcNow); - return TrueTaskResult; - } - return FalseTaskResult; - } - } -} diff --git a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs index a427f1bacd..8c700c9449 100644 --- a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs +++ b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs @@ -15,9 +15,12 @@ namespace MediaBrowser.Providers.Movies /// public class MovieProviderFromXml : BaseMetadataProvider { + internal static MovieProviderFromXml Current { get; private set; } + public MovieProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) { + Current = this; } /// diff --git a/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs b/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs new file mode 100644 index 0000000000..c6e960332d --- /dev/null +++ b/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs @@ -0,0 +1,93 @@ +using System; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; +using MediaBrowser.Providers.TV; + +namespace MediaBrowser.Providers.Savers +{ + public class EpisodeXmlSaver : IMetadataSaver + { + /// + /// Supportses the specified item. + /// + /// The item. + /// true if XXXX, false otherwise + public bool Supports(BaseItem item) + { + if (item.LocationType != LocationType.FileSystem) + { + return false; + } + + return item is Episode; + } + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + /// + /// Saves the specified item. + /// + /// The item. + /// The cancellation token. + /// Task. + public void Save(BaseItem item, CancellationToken cancellationToken) + { + var episode = (Episode)item; + + var builder = new StringBuilder(); + + builder.Append(""); + + if (!string.IsNullOrEmpty(item.Name)) + { + builder.Append("" + SecurityElement.Escape(episode.Name) + ""); + } + + if (episode.IndexNumber.HasValue) + { + builder.Append("" + SecurityElement.Escape(episode.IndexNumber.Value.ToString(_usCulture)) + ""); + } + + if (episode.ParentIndexNumber.HasValue) + { + builder.Append("" + SecurityElement.Escape(episode.ParentIndexNumber.Value.ToString(_usCulture)) + ""); + } + + if (episode.PremiereDate.HasValue) + { + builder.Append("" + SecurityElement.Escape(episode.PremiereDate.Value.ToString("yyyy-MM-dd")) + ""); + } + + XmlHelpers.AddCommonNodes(item, builder); + XmlHelpers.AppendMediaInfo(episode, builder); + + builder.Append(""); + + var xmlFilePath = GetSavePath(item); + + XmlHelpers.Save(builder, xmlFilePath); + + // Set last refreshed so that the provider doesn't trigger after the file save + EpisodeProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow); + } + + /// + /// Gets the save path. + /// + /// The item. + /// System.String. + public string GetSavePath(BaseItem item) + { + var filename = Path.ChangeExtension(Path.GetFileName(item.Path), ".xml"); + + return Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename); + } + } +} diff --git a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs new file mode 100644 index 0000000000..6790daa048 --- /dev/null +++ b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs @@ -0,0 +1,60 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using System.IO; +using System.Text; +using System.Threading; + +namespace MediaBrowser.Providers.Savers +{ + public class FolderXmlSaver : IMetadataSaver + { + /// + /// Supportses the specified item. + /// + /// The item. + /// true if XXXX, false otherwise + public bool Supports(BaseItem item) + { + if (item.LocationType != LocationType.FileSystem) + { + return false; + } + + return item is Folder && !(item is Series) && !(item is BoxSet); + } + + /// + /// Saves the specified item. + /// + /// The item. + /// The cancellation token. + /// Task. + public void Save(BaseItem item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append(""); + + XmlHelpers.AddCommonNodes(item, builder); + + builder.Append(""); + + var xmlFilePath = GetSavePath(item); + + XmlHelpers.Save(builder, xmlFilePath); + } + + /// + /// Gets the save path. + /// + /// The item. + /// System.String. + public string GetSavePath(BaseItem item) + { + return Path.Combine(item.Path, "folder.xml"); + } + } +} diff --git a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs index 7a618c74f4..d3683d2b1d 100644 --- a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs @@ -1,10 +1,12 @@ -using MediaBrowser.Controller.Entities; +using System.Text; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using MediaBrowser.Providers.Movies; +using System; using System.IO; using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers.Savers { @@ -42,15 +44,23 @@ namespace MediaBrowser.Providers.Savers /// The item. /// The cancellation token. /// Task. - public Task Save(BaseItem item, CancellationToken cancellationToken) + public void Save(BaseItem item, CancellationToken cancellationToken) { - var video = (Video)item; + var builder = new StringBuilder(); + + builder.Append(""); + + XmlHelpers.AddCommonNodes(item, builder); + + builder.Append(""); var xmlFilePath = GetSavePath(item); - return Task.Run(() => { }); - } + XmlHelpers.Save(builder, xmlFilePath); + // Set last refreshed so that the provider doesn't trigger after the file save + MovieProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow); + } public string GetSavePath(BaseItem item) { diff --git a/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs b/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs new file mode 100644 index 0000000000..53d4ac1483 --- /dev/null +++ b/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs @@ -0,0 +1,103 @@ +using System; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; +using MediaBrowser.Providers.TV; + +namespace MediaBrowser.Providers.Savers +{ + public class SeriesXmlSaver : IMetadataSaver + { + /// + /// Supportses the specified item. + /// + /// The item. + /// true if XXXX, false otherwise + public bool Supports(BaseItem item) + { + if (item.LocationType != LocationType.FileSystem) + { + return false; + } + + return item is Series; + } + + /// + /// Saves the specified item. + /// + /// The item. + /// The cancellation token. + /// Task. + public void Save(BaseItem item, CancellationToken cancellationToken) + { + var series = (Series)item; + + var builder = new StringBuilder(); + + builder.Append(""); + + var tvdb = item.GetProviderId(MetadataProviders.Tvdb); + + if (!string.IsNullOrEmpty(tvdb)) + { + builder.Append("" + SecurityElement.Escape(tvdb) + ""); + } + + if (!string.IsNullOrEmpty(item.Name)) + { + builder.Append("" + SecurityElement.Escape(item.Name) + ""); + } + + if (series.Status.HasValue) + { + builder.Append("" + SecurityElement.Escape(series.Status.Value.ToString()) + ""); + } + + if (series.Studios.Count > 0) + { + builder.Append("" + SecurityElement.Escape(item.Studios[0]) + ""); + } + + if (!string.IsNullOrEmpty(series.AirTime)) + { + builder.Append("" + SecurityElement.Escape(series.AirTime) + ""); + } + + if (series.AirDays.Count == 7) + { + builder.Append("" + SecurityElement.Escape("Daily") + ""); + } + else if (series.AirDays.Count > 0) + { + builder.Append("" + SecurityElement.Escape(series.AirDays[0].ToString()) + ""); + } + + XmlHelpers.AddCommonNodes(item, builder); + + builder.Append(""); + + var xmlFilePath = GetSavePath(item); + + XmlHelpers.Save(builder, xmlFilePath); + + // Set last refreshed so that the provider doesn't trigger after the file save + SeriesProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow); + } + + /// + /// Gets the save path. + /// + /// The item. + /// System.String. + public string GetSavePath(BaseItem item) + { + return Path.Combine(item.Path, "series.xml"); + } + } +} diff --git a/MediaBrowser.Providers/Savers/XmlHelpers.cs b/MediaBrowser.Providers/Savers/XmlHelpers.cs new file mode 100644 index 0000000000..3838f66692 --- /dev/null +++ b/MediaBrowser.Providers/Savers/XmlHelpers.cs @@ -0,0 +1,346 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Model.Entities; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security; +using System.Text; +using System.Xml; + +namespace MediaBrowser.Providers.Savers +{ + /// + /// Class XmlHelpers + /// + public static class XmlHelpers + { + /// + /// The us culture + /// + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// + /// Saves the specified XML. + /// + /// The XML. + /// The path. + public static void Save(StringBuilder xml, string path) + { + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(xml.ToString()); + + //Add the new node to the document. + xmlDocument.InsertBefore(xmlDocument.CreateXmlDeclaration("1.0", "UTF-8", "yes"), xmlDocument.DocumentElement); + + using (var streamWriter = new StreamWriter(path, false, Encoding.UTF8)) + { + xmlDocument.Save(streamWriter); + } + } + + /// + /// Adds the common nodes. + /// + /// The item. + /// The builder. + public static void AddCommonNodes(BaseItem item, StringBuilder builder) + { + if (!string.IsNullOrEmpty(item.OfficialRating)) + { + builder.Append("" + SecurityElement.Escape(item.OfficialRating) + ""); + builder.Append("" + SecurityElement.Escape(item.OfficialRating) + ""); + builder.Append("" + SecurityElement.Escape(item.OfficialRating) + ""); + } + + if (item.People.Count > 0) + { + builder.Append(""); + + foreach (var person in item.People) + { + builder.Append(""); + builder.Append("" + SecurityElement.Escape(person.Name) + ""); + builder.Append("" + SecurityElement.Escape(person.Type) + ""); + builder.Append("" + SecurityElement.Escape(person.Role) + ""); + builder.Append(""); + } + + builder.Append(""); + } + + if (!string.IsNullOrEmpty(item.DisplayMediaType)) + { + builder.Append("" + SecurityElement.Escape(item.DisplayMediaType) + ""); + } + + if (!string.IsNullOrEmpty(item.Overview)) + { + builder.Append(""); + } + + if (!string.IsNullOrEmpty(item.CustomRating)) + { + builder.Append("" + SecurityElement.Escape(item.CustomRating) + ""); + } + + if (!string.IsNullOrEmpty(item.Name) && !(item is Episode)) + { + builder.Append("" + SecurityElement.Escape(item.Name) + ""); + } + + if (!string.IsNullOrEmpty(item.ForcedSortName)) + { + builder.Append("" + SecurityElement.Escape(item.ForcedSortName) + ""); + } + + if (item.Budget.HasValue) + { + builder.Append("" + SecurityElement.Escape(item.Budget.Value.ToString(UsCulture)) + ""); + } + + if (item.Revenue.HasValue) + { + builder.Append("" + SecurityElement.Escape(item.Revenue.Value.ToString(UsCulture)) + ""); + } + + if (item.CommunityRating.HasValue) + { + builder.Append("" + SecurityElement.Escape(item.CommunityRating.Value.ToString(UsCulture)) + ""); + } + + if (item.ProductionYear.HasValue) + { + builder.Append("" + SecurityElement.Escape(item.ProductionYear.Value.ToString(UsCulture)) + ""); + } + + if (!string.IsNullOrEmpty(item.HomePageUrl)) + { + builder.Append("" + SecurityElement.Escape(item.HomePageUrl) + ""); + } + + if (!string.IsNullOrEmpty(item.AspectRatio)) + { + builder.Append("" + SecurityElement.Escape(item.AspectRatio) + ""); + } + + if (!string.IsNullOrEmpty(item.Language)) + { + builder.Append("" + SecurityElement.Escape(item.Language) + ""); + } + + if (item.RunTimeTicks.HasValue) + { + var timespan = TimeSpan.FromTicks(item.RunTimeTicks.Value); + + builder.Append("" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + ""); + builder.Append("" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + ""); + } + + if (item.Taglines.Count > 0) + { + builder.Append("" + SecurityElement.Escape(item.Taglines[0]) + ""); + + builder.Append(""); + + foreach (var tagline in item.Taglines) + { + builder.Append("" + SecurityElement.Escape(tagline) + ""); + } + + builder.Append(""); + } + + var imdb = item.GetProviderId(MetadataProviders.Imdb); + + if (!string.IsNullOrEmpty(imdb)) + { + builder.Append("" + SecurityElement.Escape(imdb) + ""); + builder.Append("" + SecurityElement.Escape(imdb) + ""); + builder.Append("" + SecurityElement.Escape(imdb) + ""); + } + + var tmdb = item.GetProviderId(MetadataProviders.Tmdb); + + if (!string.IsNullOrEmpty(tmdb)) + { + builder.Append("" + SecurityElement.Escape(tmdb) + ""); + } + + var tvcom = item.GetProviderId(MetadataProviders.Tvcom); + + if (!string.IsNullOrEmpty(tvcom)) + { + builder.Append("" + SecurityElement.Escape(tvcom) + ""); + } + + var rt = item.GetProviderId(MetadataProviders.RottenTomatoes); + + if (!string.IsNullOrEmpty(rt)) + { + builder.Append("" + SecurityElement.Escape(rt) + ""); + } + + var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection); + + if (!string.IsNullOrEmpty(tmdbCollection)) + { + builder.Append("" + SecurityElement.Escape(tmdbCollection) + ""); + } + + if (item.Genres.Count > 0) + { + builder.Append(""); + + foreach (var genre in item.Genres) + { + builder.Append("" + SecurityElement.Escape(genre) + ""); + } + + builder.Append(""); + } + + if (item.Studios.Count > 0) + { + builder.Append(""); + + foreach (var studio in item.Studios) + { + builder.Append("" + SecurityElement.Escape(studio) + ""); + } + + builder.Append(""); + } + + builder.Append("" + SecurityElement.Escape(item.DateCreated.ToString(UsCulture)) + ""); + } + + /// + /// Appends the media info. + /// + /// + /// The item. + /// The builder. + public static void AppendMediaInfo(T item, StringBuilder builder) + where T : BaseItem, IHasMediaStreams + { + builder.Append(""); + + foreach (var stream in item.MediaStreams) + { + if (stream.Type == MediaStreamType.Video) + { + builder.Append(""); + } + else if (stream.Type == MediaStreamType.Audio) + { + builder.Append(""); + } + else if (stream.Type == MediaStreamType.Subtitle) + { + builder.Append(""); + + if (!string.IsNullOrEmpty(stream.Language)) + { + builder.Append("" + SecurityElement.Escape(stream.Language) + ""); + } + + builder.Append("" + SecurityElement.Escape(stream.IsDefault.ToString()) + ""); + builder.Append("" + SecurityElement.Escape(stream.IsForced.ToString()) + ""); + + builder.Append(""); + } + } + + builder.Append(""); + } + } +} diff --git a/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs b/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs index 498088eacc..76deffa4e6 100644 --- a/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs +++ b/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs @@ -16,9 +16,12 @@ namespace MediaBrowser.Providers.TV /// public class EpisodeProviderFromXml : BaseMetadataProvider { + internal static EpisodeProviderFromXml Current { get; private set; } + public EpisodeProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) { + Current = this; } /// diff --git a/MediaBrowser.Providers/TV/SeriesProviderFromXml.cs b/MediaBrowser.Providers/TV/SeriesProviderFromXml.cs index f1e1885851..3aec76ad46 100644 --- a/MediaBrowser.Providers/TV/SeriesProviderFromXml.cs +++ b/MediaBrowser.Providers/TV/SeriesProviderFromXml.cs @@ -16,8 +16,12 @@ namespace MediaBrowser.Providers.TV /// public class SeriesProviderFromXml : BaseMetadataProvider { - public SeriesProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) + internal static SeriesProviderFromXml Current { get; private set; } + + public SeriesProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager) + : base(logManager, configurationManager) { + Current = this; } /// diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index e174b9a23d..d1b7634fbc 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -73,12 +73,6 @@ namespace MediaBrowser.Server.Implementations.Library /// The comparers. private IEnumerable Comparers { get; set; } - /// - /// Gets or sets the savers. - /// - /// The savers. - private IEnumerable Savers { get; set; } - /// /// Gets the active item repository /// @@ -197,15 +191,13 @@ namespace MediaBrowser.Server.Implementations.Library /// The item comparers. /// The prescan tasks. /// The postscan tasks. - /// The savers. public void AddParts(IEnumerable rules, IEnumerable pluginFolders, IEnumerable resolvers, IEnumerable introProviders, IEnumerable itemComparers, IEnumerable prescanTasks, - IEnumerable postscanTasks, - IEnumerable savers) + IEnumerable postscanTasks) { EntityResolutionIgnoreRules = rules; PluginFolderCreators = pluginFolders; @@ -214,7 +206,6 @@ namespace MediaBrowser.Server.Implementations.Library Comparers = itemComparers; PrescanTasks = prescanTasks; PostscanTasks = postscanTasks; - Savers = savers; } /// @@ -589,7 +580,7 @@ namespace MediaBrowser.Server.Implementations.Library /// UserRootFolder. public UserRootFolder GetUserRootFolder(string userRootPath) { - return _userRootFolders.GetOrAdd(userRootPath, key => RetrieveItem(userRootPath.GetMBId(typeof(UserRootFolder)), typeof(UserRootFolder)) as UserRootFolder ?? + return _userRootFolders.GetOrAdd(userRootPath, key => RetrieveItem(userRootPath.GetMBId(typeof(UserRootFolder)), typeof(UserRootFolder)) as UserRootFolder ?? (UserRootFolder)ResolvePath(new DirectoryInfo(userRootPath))); } @@ -649,7 +640,7 @@ namespace MediaBrowser.Server.Implementations.Library { return GetItemByName(ConfigurationManager.ApplicationPaths.MusicGenrePath, name, CancellationToken.None, allowSlowProviders); } - + /// /// Gets a Genre /// @@ -1001,7 +992,7 @@ namespace MediaBrowser.Server.Implementations.Library await RunPrescanTasks(progress, cancellationToken).ConfigureAwait(false); progress.Report(15); - + var innerProgress = new ActionableProgress(); innerProgress.RegisterAction(pct => progress.Report(15 + pct * .65)); @@ -1010,7 +1001,7 @@ namespace MediaBrowser.Server.Implementations.Library await RootFolder.ValidateChildren(innerProgress, cancellationToken, recursive: true).ConfigureAwait(false); progress.Report(80); - + // Run post-scan tasks await RunPostScanTasks(progress, cancellationToken).ConfigureAwait(false); @@ -1044,7 +1035,7 @@ namespace MediaBrowser.Server.Implementations.Library progress.Report(2 + percent * .13); } }); - + try { await i.Run(innerProgress, cancellationToken); @@ -1301,11 +1292,7 @@ namespace MediaBrowser.Server.Implementations.Library foreach (var item in list) { UpdateItemInLibraryCache(item); - } - - foreach (var item in list) - { - await OnItemUpdated(item, CancellationToken.None).ConfigureAwait(false); + OnItemUpdated(item); } } @@ -1354,25 +1341,9 @@ namespace MediaBrowser.Server.Implementations.Library /// Called when [item updated]. /// /// The item. - /// The cancellation token. /// Task. - private async Task OnItemUpdated(BaseItem item, CancellationToken cancellationToken) + private void OnItemUpdated(BaseItem item) { - if (ConfigurationManager.Configuration.SaveLocalMeta && item.LocationType == LocationType.FileSystem) - { - foreach (var saver in Savers.Where(i => i.Supports(item))) - { - try - { - await saver.Save(item, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in metadata saver", ex); - } - } - } - if (ItemUpdated != null) { try diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs index beab6200f2..3c1a7aa1e9 100644 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -3,7 +3,9 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Concurrent; @@ -58,6 +60,8 @@ namespace MediaBrowser.Server.Implementations.Providers /// The metadata providers enumerable. private BaseMetadataProvider[] MetadataProviders { get; set; } + private IEnumerable _savers; + /// /// Initializes a new instance of the class. /// @@ -65,7 +69,8 @@ namespace MediaBrowser.Server.Implementations.Providers /// The configuration manager. /// The directory watchers. /// The log manager. - public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager) + /// The library manager. + public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager, ILibraryManager libraryManager) { _logger = logManager.GetLogger("ProviderManager"); _httpClient = httpClient; @@ -74,6 +79,37 @@ namespace MediaBrowser.Server.Implementations.Providers _remoteImageCache = new FileSystemRepository(configurationManager.ApplicationPaths.DownloadedImagesDataPath); configurationManager.ConfigurationUpdated += configurationManager_ConfigurationUpdated; + + libraryManager.ItemUpdated += libraryManager_ItemUpdated; + } + + void libraryManager_ItemUpdated(object sender, ItemChangeEventArgs e) + { + var item = e.Item; + + if (ConfigurationManager.Configuration.SaveLocalMeta && item.LocationType == LocationType.FileSystem) + { + foreach (var saver in _savers.Where(i => i.Supports(item))) + { + var path = saver.GetSavePath(item); + + _directoryWatchers.TemporarilyIgnore(path); + + try + { + saver.Save(item, CancellationToken.None); + } + catch (Exception ex) + { + _logger.ErrorException("Error in metadata saver", ex); + } + finally + { + _directoryWatchers.RemoveTempIgnore(path); + } + } + } + } /// @@ -91,9 +127,12 @@ namespace MediaBrowser.Server.Implementations.Providers /// Adds the metadata providers. /// /// The providers. - public void AddMetadataProviders(IEnumerable providers) + /// The savers. + public void AddParts(IEnumerable providers, + IEnumerable savers) { MetadataProviders = providers.OrderBy(e => e.Priority).ToArray(); + _savers = savers; } /// diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 9f28006bac..09e33d7fdf 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -271,7 +271,7 @@ namespace MediaBrowser.ServerApplication DirectoryWatchers = new DirectoryWatchers(LogManager, TaskManager, LibraryManager, ServerConfigurationManager); RegisterSingleInstance(DirectoryWatchers); - ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, DirectoryWatchers, LogManager); + ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, DirectoryWatchers, LogManager, LibraryManager); RegisterSingleInstance(ProviderManager); RegisterSingleInstance(() => new LuceneSearchEngine(ApplicationPaths, LogManager, LibraryManager)); @@ -397,10 +397,10 @@ namespace MediaBrowser.ServerApplication GetExports(), GetExports(), GetExports(), - GetExports(), - GetExports()); + GetExports()); - ProviderManager.AddMetadataProviders(GetExports().ToArray()); + ProviderManager.AddParts(GetExports().ToArray(), + GetExports()); } ///