diff options
33 files changed, 385 insertions, 160 deletions
diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs index 151f7016c..dd15dbc30 100644 --- a/MediaBrowser.Api/LibraryService.cs +++ b/MediaBrowser.Api/LibraryService.cs @@ -283,8 +283,8 @@ namespace MediaBrowser.Api var item = DtoBuilder.GetItemByClientId(request.ItemId, _userManager, _libraryManager); UpdateItem(request, item); - - return _libraryManager.UpdateItem(item, CancellationToken.None); + + return _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None); } public void Post(UpdatePerson request) @@ -300,7 +300,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, CancellationToken.None).ConfigureAwait(false); + await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateArtist request) @@ -316,7 +316,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, CancellationToken.None).ConfigureAwait(false); + await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateStudio request) @@ -332,7 +332,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, CancellationToken.None).ConfigureAwait(false); + await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateMusicGenre request) @@ -348,7 +348,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, CancellationToken.None).ConfigureAwait(false); + await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateGenre request) @@ -364,13 +364,19 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, CancellationToken.None).ConfigureAwait(false); + await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } private void UpdateItem(BaseItemDto request, BaseItem item) { item.Name = request.Name; - item.ForcedSortName = request.SortName; + + // Only set the forced value if they changed it, or there's already one + if (!string.Equals(item.SortName, request.SortName) || !string.IsNullOrEmpty(item.ForcedSortName)) + { + item.ForcedSortName = request.SortName; + } + item.DisplayMediaType = request.DisplayMediaType; item.CommunityRating = request.CommunityRating; item.HomePageUrl = request.HomePageUrl; diff --git a/MediaBrowser.Controller/Dto/DtoBuilder.cs b/MediaBrowser.Controller/Dto/DtoBuilder.cs index 2e28d7ad6..2bd338431 100644 --- a/MediaBrowser.Controller/Dto/DtoBuilder.cs +++ b/MediaBrowser.Controller/Dto/DtoBuilder.cs @@ -389,6 +389,11 @@ namespace MediaBrowser.Controller.Dto dto.SortName = item.SortName; } + if (fields.Contains(ItemFields.CustomRating)) + { + dto.CustomRating = item.CustomRating; + } + if (fields.Contains(ItemFields.Taglines)) { dto.Taglines = item.Taglines; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index dbfeff747..695ea2583 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -834,12 +834,15 @@ namespace MediaBrowser.Controller.Entities cancellationToken.ThrowIfCancellationRequested(); // Get the result from the item task - var changed = await itemRefreshTask.ConfigureAwait(false); + var updateReason = await itemRefreshTask.ConfigureAwait(false); + + var changed = updateReason.HasValue; if (changed || forceSave || themeSongsChanged || themeVideosChanged || localTrailersChanged) { cancellationToken.ThrowIfCancellationRequested(); - await LibraryManager.UpdateItem(this, cancellationToken).ConfigureAwait(false); + + await LibraryManager.UpdateItem(this, updateReason ?? ItemUpdateType.Unspecified, cancellationToken).ConfigureAwait(false); } return changed; diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index a2c5f98b8..79a813bb5 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -315,7 +315,9 @@ namespace MediaBrowser.Controller.Entities ResolveArgs = null; } - var changed = await ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh, allowSlowProviders).ConfigureAwait(false); + var updateReason = await ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh, allowSlowProviders).ConfigureAwait(false); + + var changed = updateReason.HasValue; if (changed || forceSave) { diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 46a6d38df..f1d5dce89 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -152,13 +152,15 @@ namespace MediaBrowser.Controller.Library /// <param name="itemComparers">The item comparers.</param> /// <param name="prescanTasks">The prescan tasks.</param> /// <param name="postscanTasks">The postscan tasks.</param> + /// <param name="savers">The savers.</param> void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IVirtualFolderCreator> pluginFolders, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers, IEnumerable<ILibraryPrescanTask> prescanTasks, - IEnumerable<ILibraryPostScanTask> postscanTasks); + IEnumerable<ILibraryPostScanTask> postscanTasks, + IEnumerable<IMetadataSaver> savers); /// <summary> /// Sorts the specified items. @@ -205,9 +207,10 @@ namespace MediaBrowser.Controller.Library /// Updates the item. /// </summary> /// <param name="item">The item.</param> + /// <param name="updateReason">The update reason.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task UpdateItem(BaseItem item, CancellationToken cancellationToken); + Task UpdateItem(BaseItem item, ItemUpdateType updateReason, CancellationToken cancellationToken); /// <summary> /// Retrieves the item. diff --git a/MediaBrowser.Controller/Library/ItemUpdateType.cs b/MediaBrowser.Controller/Library/ItemUpdateType.cs new file mode 100644 index 000000000..56ae2cbbd --- /dev/null +++ b/MediaBrowser.Controller/Library/ItemUpdateType.cs @@ -0,0 +1,13 @@ +using System; + +namespace MediaBrowser.Controller.Library +{ + [Flags] + public enum ItemUpdateType + { + Unspecified = 1, + MetadataImport = 2, + ImageUpdate = 4, + MetadataEdit = 16 + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 0bd82eaa7..2c6a6df08 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -81,6 +81,7 @@ <Compile Include="Library\ILibraryPostScanTask.cs" /> <Compile Include="Library\ILibraryPrescanTask.cs" /> <Compile Include="Library\IMetadataSaver.cs" /> + <Compile Include="Library\ItemUpdateType.cs" /> <Compile Include="Localization\ILocalizationManager.cs" /> <Compile Include="Reflection\TypeMapper.cs" /> <Compile Include="Session\ISessionManager.cs" /> diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 29cf43f28..6c3d08a14 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -124,6 +124,17 @@ namespace MediaBrowser.Controller.Providers break; } + case "CriticRating": + { + var text = reader.ReadElementContentAsString(); + float value; + if (float.TryParse(text, NumberStyles.Any, _usCulture, out value)) + { + item.CriticRating = value; + } + + break; + } case "Budget": { var text = reader.ReadElementContentAsString(); @@ -163,6 +174,18 @@ namespace MediaBrowser.Controller.Providers break; } + case "CriticRatingSummary": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + item.CriticRatingSummary = val; + } + + break; + } + case "TagLine": { var tagline = reader.ReadElementContentAsString(); diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs index 625910c0b..e415c6859 100644 --- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -75,6 +75,11 @@ namespace MediaBrowser.Controller.Providers } } + public virtual ItemUpdateType ItemUpdateType + { + get { return RequiresInternet ? ItemUpdateType.MetadataEdit : ItemUpdateType.MetadataImport; } + } + /// <summary> /// Gets a value indicating whether [refresh on version change]. /// </summary> diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 7f80973e9..643dbe1c2 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -55,15 +55,13 @@ namespace MediaBrowser.Controller.Providers /// <param name="force">if set to <c>true</c> [force].</param> /// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param> /// <returns>Task{System.Boolean}.</returns> - Task<bool> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true); + Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true); /// <summary> /// Adds the metadata providers. /// </summary> /// <param name="providers">The providers.</param> - /// <param name="savers">The savers.</param> - void AddParts(IEnumerable<BaseMetadataProvider> providers, - IEnumerable<IMetadataSaver> savers); + void AddParts(IEnumerable<BaseMetadataProvider> providers); /// <summary> /// Gets the save path. diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index d136af8f0..a8a58d6a6 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -27,6 +27,11 @@ namespace MediaBrowser.Model.Querying CriticRatingSummary, /// <summary> + /// The custom rating + /// </summary> + CustomRating, + + /// <summary> /// The date created of the item /// </summary> DateCreated, diff --git a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs index 87c450d55..883461cd0 100644 --- a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -22,6 +23,14 @@ namespace MediaBrowser.Providers { } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Supportses the specified item. /// </summary> diff --git a/MediaBrowser.Providers/ImagesByNameProvider.cs b/MediaBrowser.Providers/ImagesByNameProvider.cs index dad3a25f3..228ca94c5 100644 --- a/MediaBrowser.Providers/ImagesByNameProvider.cs +++ b/MediaBrowser.Providers/ImagesByNameProvider.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; @@ -22,6 +23,14 @@ namespace MediaBrowser.Providers { } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Supportses the specified item. /// </summary> diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index c20a31b7e..ab3fbff55 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -1,5 +1,4 @@ -using System.IO; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; @@ -11,6 +10,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Concurrent; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -113,6 +113,14 @@ namespace MediaBrowser.Providers.MediaInfo get { return MetadataProviderPriority.Last; } } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// </summary> @@ -193,8 +201,6 @@ namespace MediaBrowser.Providers.MediaInfo // Image is already in the cache item.PrimaryImagePath = path; - - await _libraryManager.UpdateItem(item, cancellationToken).ConfigureAwait(false); } /// <summary> diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs index be105b63a..1a1c34928 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs @@ -5,6 +5,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -61,6 +62,14 @@ namespace MediaBrowser.Providers.Movies Current = this; } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Gets a value indicating whether [refresh on version change]. /// </summary> diff --git a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs index f092decdf..2c9fbcec8 100644 --- a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -85,6 +86,14 @@ namespace MediaBrowser.Providers.Movies return item is Movie || item is BoxSet || item is MusicVideo; } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Gets a value indicating whether [requires internet]. /// </summary> diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 8fecf6f44..43eae7ca2 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -226,7 +226,7 @@ namespace MediaBrowser.Providers.Movies /// </summary> /// <param name="item">The item.</param> /// <returns><c>true</c> if [has alt meta] [the specified item]; otherwise, <c>false</c>.</returns> - private bool HasAltMeta(BaseItem item) + internal static bool HasAltMeta(BaseItem item) { return item.LocationType == LocationType.FileSystem && item.ResolveArgs.ContainsMetaFileByName(AltMetaFileName); } diff --git a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs index c88169c80..3e12b2d87 100644 --- a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs +++ b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -73,6 +72,17 @@ namespace MediaBrowser.Providers.Movies } } + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + { + // These values are now saved in movie.xml, so don't refresh if they're present + if (MovieDbProvider.HasAltMeta(item) && item.CriticRating.HasValue && !string.IsNullOrEmpty(item.CriticRatingSummary)) + { + return false; + } + + return base.NeedsRefreshInternal(item, providerInfo); + } + /// <summary> /// Supports the specified item. /// </summary> diff --git a/MediaBrowser.Providers/Movies/TmdbPersonProvider.cs b/MediaBrowser.Providers/Movies/TmdbPersonProvider.cs index fbf084218..b5c11acb1 100644 --- a/MediaBrowser.Providers/Movies/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Movies/TmdbPersonProvider.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -66,6 +67,14 @@ namespace MediaBrowser.Providers.Movies } } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataEdit; + } + } + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) { if (HasAltMeta(item)) diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs index 299ae2d06..d7990f431 100644 --- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -64,6 +65,14 @@ namespace MediaBrowser.Providers.Music return item is MusicAlbum; } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Gets a value indicating whether [refresh on version change]. /// </summary> diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 1685846d6..f310934af 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -5,6 +5,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -63,6 +64,14 @@ namespace MediaBrowser.Providers.Music /// </summary> protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/artist/{0}/{1}/xml/all/1/1"; + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Supportses the specified item. /// </summary> diff --git a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs index 32ec765b0..c180c2267 100644 --- a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs @@ -59,6 +59,7 @@ namespace MediaBrowser.Providers.Savers builder.Append("<Title>"); XmlSaverHelpers.AddCommonNodes(item, builder); + XmlSaverHelpers.AppendMediaInfo((Video)item, builder); builder.Append("</Title>"); diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index 88d6ebec0..2ceb82d71 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -89,9 +89,35 @@ namespace MediaBrowser.Providers.Savers Directory.CreateDirectory(parentPath); } - using (var streamWriter = new StreamWriter(path, false, Encoding.UTF8)) + var wasHidden = false; + + var file = new FileInfo(path); + + // This will fail if the file is hidden + if (file.Exists) + { + if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + file.Attributes &= ~FileAttributes.Hidden; + + wasHidden = true; + } + } + + using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + using (var streamWriter = new StreamWriter(filestream, Encoding.UTF8)) + { + xmlDocument.Save(streamWriter); + } + } + + if (wasHidden) { - xmlDocument.Save(streamWriter); + file.Refresh(); + + // Add back the attribute + file.Attributes |= FileAttributes.Hidden; } } @@ -128,25 +154,21 @@ namespace MediaBrowser.Providers.Savers builder.Append("<certification>" + SecurityElement.Escape(item.OfficialRating) + "</certification>"); } - if (item.People.Count > 0) - { - builder.Append("<Persons>"); + builder.Append("<Added>" + SecurityElement.Escape(item.DateCreated.ToString(UsCulture)) + "</Added>"); - foreach (var person in item.People) - { - builder.Append("<Person>"); - builder.Append("<Name>" + SecurityElement.Escape(person.Name) + "</Name>"); - builder.Append("<Type>" + SecurityElement.Escape(person.Type) + "</Type>"); - builder.Append("<Role>" + SecurityElement.Escape(person.Role) + "</Role>"); - builder.Append("</Person>"); - } + if (!string.IsNullOrEmpty(item.DisplayMediaType)) + { + builder.Append("<Type>" + SecurityElement.Escape(item.DisplayMediaType) + "</Type>"); + } - builder.Append("</Persons>"); + if (item.CriticRating.HasValue) + { + builder.Append("<CriticRating>" + SecurityElement.Escape(item.CriticRating.Value.ToString(UsCulture)) + "</CriticRating>"); } - if (!string.IsNullOrEmpty(item.DisplayMediaType)) + if (!string.IsNullOrEmpty(item.CriticRatingSummary)) { - builder.Append("<Type>" + SecurityElement.Escape(item.DisplayMediaType) + "</Type>"); + builder.Append("<CriticRatingSummary><![CDATA[" + item.Overview + "]]></CriticRatingSummary>"); } if (!string.IsNullOrEmpty(item.Overview)) @@ -209,28 +231,17 @@ namespace MediaBrowser.Providers.Savers builder.Append("<Language>" + SecurityElement.Escape(item.Language) + "</Language>"); } - if (item.RunTimeTicks.HasValue) + // Use original runtime here, actual file runtime later in MediaInfo + var runTimeTicks = item.OriginalRunTimeTicks ?? item.RunTimeTicks; + + if (runTimeTicks.HasValue) { - var timespan = TimeSpan.FromTicks(item.RunTimeTicks.Value); + var timespan = TimeSpan.FromTicks(runTimeTicks.Value); builder.Append("<RunningTime>" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + "</RunningTime>"); builder.Append("<Runtime>" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + "</Runtime>"); } - if (item.Taglines.Count > 0) - { - builder.Append("<TagLine>" + SecurityElement.Escape(item.Taglines[0]) + "</TagLine>"); - - builder.Append("<TagLines>"); - - foreach (var tagline in item.Taglines) - { - builder.Append("<Tagline>" + SecurityElement.Escape(tagline) + "</Tagline>"); - } - - builder.Append("</TagLines>"); - } - var imdb = item.GetProviderId(MetadataProviders.Imdb); if (!string.IsNullOrEmpty(imdb)) @@ -275,6 +286,20 @@ namespace MediaBrowser.Providers.Savers builder.Append("<CollectionNumber>" + SecurityElement.Escape(tmdbCollection) + "</CollectionNumber>"); } + if (item.Taglines.Count > 0) + { + builder.Append("<TagLine>" + SecurityElement.Escape(item.Taglines[0]) + "</TagLine>"); + + builder.Append("<TagLines>"); + + foreach (var tagline in item.Taglines) + { + builder.Append("<Tagline>" + SecurityElement.Escape(tagline) + "</Tagline>"); + } + + builder.Append("</TagLines>"); + } + if (item.Genres.Count > 0) { builder.Append("<Genres>"); @@ -311,7 +336,22 @@ namespace MediaBrowser.Providers.Savers builder.Append("</Tags>"); } - builder.Append("<Added>" + SecurityElement.Escape(item.DateCreated.ToString(UsCulture)) + "</Added>"); + if (item.People.Count > 0) + { + builder.Append("<Persons>"); + + foreach (var person in item.People) + { + builder.Append("<Person>"); + builder.Append("<Name>" + SecurityElement.Escape(person.Name) + "</Name>"); + builder.Append("<Type>" + SecurityElement.Escape(person.Type) + "</Type>"); + builder.Append("<Role>" + SecurityElement.Escape(person.Role) + "</Role>"); + builder.Append("</Person>"); + } + + builder.Append("</Persons>"); + } + } /// <summary> diff --git a/MediaBrowser.Providers/TV/EpisodeImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/TV/EpisodeImageFromMediaLocationProvider.cs index 5b1490750..8eb01a143 100644 --- a/MediaBrowser.Providers/TV/EpisodeImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Providers/TV/EpisodeImageFromMediaLocationProvider.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using System; @@ -21,6 +22,14 @@ namespace MediaBrowser.Providers.TV { } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Supportses the specified item. /// </summary> diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs index ccdfc2a81..d0a0735fb 100644 --- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -35,6 +36,14 @@ namespace MediaBrowser.Providers.TV _providerManager = providerManager; } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Supportses the specified item. /// </summary> diff --git a/MediaBrowser.Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs index 878cd26e2..6dc61b35d 100644 --- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtTVProvider.cs @@ -4,6 +4,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -48,6 +49,14 @@ namespace MediaBrowser.Providers.TV return item is Series; } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Needses the refresh internal. /// </summary> diff --git a/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs b/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs index 32345cd09..a3d44a032 100644 --- a/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs @@ -61,6 +61,14 @@ namespace MediaBrowser.Providers.TV return item is Episode; } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataEdit; + } + } + /// <summary> /// Gets the priority. /// </summary> diff --git a/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs b/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs index 179354c08..f8399ebdf 100644 --- a/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/RemoteSeasonProvider.cs @@ -70,6 +70,14 @@ namespace MediaBrowser.Providers.TV } } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Gets a value indicating whether [refresh on version change]. /// </summary> diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs index bf57d3c8c..2d8bf5ed6 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs @@ -80,6 +80,14 @@ namespace MediaBrowser.Providers.TV } } + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + /// <summary> /// Gets a value indicating whether [refresh on version change]. /// </summary> diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index d1b7634fb..0465cb5c3 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -128,6 +128,10 @@ namespace MediaBrowser.Server.Implementations.Library /// <value>The by reference items.</value> private ConcurrentDictionary<Guid, BaseItem> ByReferenceItems { get; set; } + private IEnumerable<IMetadataSaver> _savers; + + private readonly Func<IDirectoryWatchers> _directoryWatchersFactory; + /// <summary> /// The _library items cache /// </summary> @@ -167,13 +171,14 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="userManager">The user manager.</param> /// <param name="configurationManager">The configuration manager.</param> /// <param name="userDataRepository">The user data repository.</param> - public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataRepository userDataRepository) + public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataRepository userDataRepository, Func<IDirectoryWatchers> directoryWatchersFactory) { _logger = logger; _taskManager = taskManager; _userManager = userManager; ConfigurationManager = configurationManager; _userDataRepository = userDataRepository; + _directoryWatchersFactory = directoryWatchersFactory; ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>(); ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -191,13 +196,15 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="itemComparers">The item comparers.</param> /// <param name="prescanTasks">The prescan tasks.</param> /// <param name="postscanTasks">The postscan tasks.</param> + /// <param name="savers">The savers.</param> public void AddParts(IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IVirtualFolderCreator> pluginFolders, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers, IEnumerable<ILibraryPrescanTask> prescanTasks, - IEnumerable<ILibraryPostScanTask> postscanTasks) + IEnumerable<ILibraryPostScanTask> postscanTasks, + IEnumerable<IMetadataSaver> savers) { EntityResolutionIgnoreRules = rules; PluginFolderCreators = pluginFolders; @@ -206,6 +213,7 @@ namespace MediaBrowser.Server.Implementations.Library Comparers = itemComparers; PrescanTasks = prescanTasks; PostscanTasks = postscanTasks; + _savers = savers; } /// <summary> @@ -326,7 +334,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="newName">The new name.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private Task UpdateSeasonZeroNames(string newName, CancellationToken cancellationToken) + private async Task UpdateSeasonZeroNames(string newName, CancellationToken cancellationToken) { var seasons = RootFolder.RecursiveChildren .OfType<Season>() @@ -336,9 +344,16 @@ namespace MediaBrowser.Server.Implementations.Library foreach (var season in seasons) { season.Name = newName; - } - return UpdateItems(seasons, cancellationToken); + try + { + await UpdateItem(season, ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error saving {0}", ex, season.Path); + } + } } /// <summary> @@ -1278,33 +1293,35 @@ namespace MediaBrowser.Server.Implementations.Library } /// <summary> - /// Updates the items. + /// Updates the item. /// </summary> - /// <param name="items">The items.</param> + /// <param name="item">The item.</param> + /// <param name="updateReason">The update reason.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private async Task UpdateItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken) + public async Task UpdateItem(BaseItem item, ItemUpdateType updateReason, CancellationToken cancellationToken) { - var list = items.ToList(); + await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false); - await ItemRepository.SaveItems(list, cancellationToken).ConfigureAwait(false); + UpdateItemInLibraryCache(item); - foreach (var item in list) + // If metadata was downloaded or edited, save external metadata + if ((updateReason & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit) { - UpdateItemInLibraryCache(item); - OnItemUpdated(item); + await SaveMetadata(item).ConfigureAwait(false); } - } - /// <summary> - /// Updates the item. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task UpdateItem(BaseItem item, CancellationToken cancellationToken) - { - return UpdateItems(new[] { item }, cancellationToken); + if (ItemUpdated != null) + { + try + { + ItemUpdated(this, new ItemChangeEventArgs { Item = item }); + } + catch (Exception ex) + { + _logger.ErrorException("Error in ItemUpdated event handler", ex); + } + } } /// <summary> @@ -1337,22 +1354,38 @@ namespace MediaBrowser.Server.Implementations.Library return ItemRepository.RetrieveItem(id, type); } + private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); + /// <summary> - /// Called when [item updated]. + /// Saves the metadata. /// </summary> /// <param name="item">The item.</param> /// <returns>Task.</returns> - private void OnItemUpdated(BaseItem item) + private async Task SaveMetadata(BaseItem item) { - if (ItemUpdated != null) + foreach (var saver in _savers.Where(i => i.Supports(item))) { + var path = saver.GetSavePath(item); + + var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1)); + + var directoryWatchers = _directoryWatchersFactory(); + + await semaphore.WaitAsync().ConfigureAwait(false); + try { - ItemUpdated(this, new ItemChangeEventArgs { Item = item }); + directoryWatchers.TemporarilyIgnore(path); + saver.Save(item, CancellationToken.None); } catch (Exception ex) { - _logger.ErrorException("Error in ItemUpdated event handler", ex); + _logger.ErrorException("Error in metadata saver", ex); + } + finally + { + directoryWatchers.RemoveTempIgnore(path); + semaphore.Release(); } } } diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs index 01c0659b6..ee8bb4c09 100644 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -60,8 +60,6 @@ namespace MediaBrowser.Server.Implementations.Providers /// <value>The metadata providers enumerable.</value> private BaseMetadataProvider[] MetadataProviders { get; set; } - private IEnumerable<IMetadataSaver> _savers; - /// <summary> /// Initializes a new instance of the <see cref="ProviderManager" /> class. /// </summary> @@ -79,44 +77,6 @@ namespace MediaBrowser.Server.Implementations.Providers _remoteImageCache = new FileSystemRepository(configurationManager.ApplicationPaths.DownloadedImagesDataPath); configurationManager.ConfigurationUpdated += configurationManager_ConfigurationUpdated; - - libraryManager.ItemUpdated += libraryManager_ItemUpdated; - } - - private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); - - /// <summary> - /// Handles the ItemUpdated event of the libraryManager control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param> - async void libraryManager_ItemUpdated(object sender, ItemChangeEventArgs e) - { - var item = e.Item; - - foreach (var saver in _savers.Where(i => i.Supports(item))) - { - var path = saver.GetSavePath(item); - - var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1)); - - await semaphore.WaitAsync().ConfigureAwait(false); - - try - { - _directoryWatchers.TemporarilyIgnore(path); - saver.Save(item, CancellationToken.None); - } - catch (Exception ex) - { - _logger.ErrorException("Error in metadata saver", ex); - } - finally - { - _directoryWatchers.RemoveTempIgnore(path); - semaphore.Release(); - } - } } /// <summary> @@ -134,12 +94,9 @@ namespace MediaBrowser.Server.Implementations.Providers /// Adds the metadata providers. /// </summary> /// <param name="providers">The providers.</param> - /// <param name="savers">The savers.</param> - public void AddParts(IEnumerable<BaseMetadataProvider> providers, - IEnumerable<IMetadataSaver> savers) + public void AddParts(IEnumerable<BaseMetadataProvider> providers) { MetadataProviders = providers.OrderBy(e => e.Priority).ToArray(); - _savers = savers; } /// <summary> @@ -150,18 +107,14 @@ namespace MediaBrowser.Server.Implementations.Providers /// <param name="force">if set to <c>true</c> [force].</param> /// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param> /// <returns>Task{System.Boolean}.</returns> - public async Task<bool> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true) + public async Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true) { if (item == null) { throw new ArgumentNullException("item"); } - // Allow providers of the same priority to execute in parallel - MetadataProviderPriority? currentPriority = null; - var currentTasks = new List<Task<bool>>(); - - var result = false; + ItemUpdateType? result = null; cancellationToken.ThrowIfCancellationRequested(); @@ -188,15 +141,6 @@ namespace MediaBrowser.Server.Implementations.Providers continue; } - // When a new priority is reached, await the ones that are currently running and clear the list - if (currentPriority.HasValue && currentPriority.Value != provider.Priority && currentTasks.Count > 0) - { - var results = await Task.WhenAll(currentTasks).ConfigureAwait(false); - result |= results.Contains(true); - - currentTasks.Clear(); - } - // Put this check below the await because the needs refresh of the next tier of providers may depend on the previous ones running // This is the case for the fan art provider which depends on the movie and tv providers having run before them if (provider.RequiresInternet && item.DontFetchMeta) @@ -216,14 +160,19 @@ namespace MediaBrowser.Server.Implementations.Providers _logger.Error("Error determining NeedsRefresh for {0}", ex, item.Path); } - currentTasks.Add(FetchAsync(provider, item, force, cancellationToken)); - currentPriority = provider.Priority; - } + var updateType = await FetchAsync(provider, item, force, cancellationToken).ConfigureAwait(false); - if (currentTasks.Count > 0) - { - var results = await Task.WhenAll(currentTasks).ConfigureAwait(false); - result |= results.Contains(true); + if (updateType.HasValue) + { + if (result.HasValue) + { + result = result.Value | updateType.Value; + } + else + { + result = updateType; + } + } } return result; @@ -238,7 +187,7 @@ namespace MediaBrowser.Server.Implementations.Providers /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> /// <exception cref="System.ArgumentNullException"></exception> - private async Task<bool> FetchAsync(BaseMetadataProvider provider, BaseItem item, bool force, CancellationToken cancellationToken) + private async Task<ItemUpdateType?> FetchAsync(BaseMetadataProvider provider, BaseItem item, bool force, CancellationToken cancellationToken) { if (item == null) { @@ -256,7 +205,14 @@ namespace MediaBrowser.Server.Implementations.Providers try { - return await provider.FetchAsync(item, force, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token).ConfigureAwait(false); + var changed = await provider.FetchAsync(item, force, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token).ConfigureAwait(false); + + if (changed) + { + return provider.ItemUpdateType; + } + + return null; } catch (OperationCanceledException ex) { @@ -268,14 +224,15 @@ namespace MediaBrowser.Server.Implementations.Providers throw; } - return false; + return null; } catch (Exception ex) { _logger.ErrorException("{0} failed refreshing {1}", ex, provider.GetType().Name, item.Name); provider.SetLastRefreshed(item, DateTime.UtcNow, ProviderRefreshStatus.Failure); - return true; + + return ItemUpdateType.Unspecified; } finally { diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs index d63494c1e..dc8425ac8 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs @@ -294,7 +294,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks // Image is already in the cache item.PrimaryImagePath = path; - await _libraryManager.UpdateItem(item, cancellationToken).ConfigureAwait(false); + await _libraryManager.UpdateItem(item, ItemUpdateType.ImageUpdate, cancellationToken).ConfigureAwait(false); } else { diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 09e33d7fd..70cd319ca 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -262,7 +262,7 @@ namespace MediaBrowser.ServerApplication UserManager = new UserManager(Logger, ServerConfigurationManager); RegisterSingleInstance(UserManager); - LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataRepository); + LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataRepository, () => DirectoryWatchers); RegisterSingleInstance(LibraryManager); InstallationManager = new InstallationManager(HttpClient, PackageManager, JsonSerializer, Logger, this); @@ -397,10 +397,10 @@ namespace MediaBrowser.ServerApplication GetExports<IIntroProvider>(), GetExports<IBaseItemComparer>(), GetExports<ILibraryPrescanTask>(), - GetExports<ILibraryPostScanTask>()); - - ProviderManager.AddParts(GetExports<BaseMetadataProvider>().ToArray(), + GetExports<ILibraryPostScanTask>(), GetExports<IMetadataSaver>()); + + ProviderManager.AddParts(GetExports<BaseMetadataProvider>().ToArray()); } /// <summary> |
