Browse Source

Only fire metadata savers when appropriate

Luke Pulverenti 12 năm trước cách đây
mục cha
commit
92cd71143d
33 tập tin đã thay đổi với 385 bổ sung160 xóa
  1. 14 8
      MediaBrowser.Api/LibraryService.cs
  2. 5 0
      MediaBrowser.Controller/Dto/DtoBuilder.cs
  3. 5 2
      MediaBrowser.Controller/Entities/BaseItem.cs
  4. 3 1
      MediaBrowser.Controller/Entities/User.cs
  5. 5 2
      MediaBrowser.Controller/Library/ILibraryManager.cs
  6. 13 0
      MediaBrowser.Controller/Library/ItemUpdateType.cs
  7. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  8. 23 0
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  9. 5 0
      MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
  10. 2 4
      MediaBrowser.Controller/Providers/IProviderManager.cs
  11. 5 0
      MediaBrowser.Model/Querying/ItemFields.cs
  12. 9 0
      MediaBrowser.Providers/ImageFromMediaLocationProvider.cs
  13. 9 0
      MediaBrowser.Providers/ImagesByNameProvider.cs
  14. 10 4
      MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
  15. 9 0
      MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
  16. 9 0
      MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
  17. 1 1
      MediaBrowser.Providers/Movies/MovieDbProvider.cs
  18. 12 2
      MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
  19. 9 0
      MediaBrowser.Providers/Movies/TmdbPersonProvider.cs
  20. 9 0
      MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
  21. 9 0
      MediaBrowser.Providers/Music/FanArtArtistProvider.cs
  22. 1 0
      MediaBrowser.Providers/Savers/MovieXmlSaver.cs
  23. 73 33
      MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
  24. 9 0
      MediaBrowser.Providers/TV/EpisodeImageFromMediaLocationProvider.cs
  25. 9 0
      MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
  26. 9 0
      MediaBrowser.Providers/TV/FanArtTVProvider.cs
  27. 8 0
      MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs
  28. 8 0
      MediaBrowser.Providers/TV/RemoteSeasonProvider.cs
  29. 8 0
      MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
  30. 61 28
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  31. 27 70
      MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
  32. 1 1
      MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs
  33. 4 4
      MediaBrowser.ServerApplication/ApplicationHost.cs

+ 14 - 8
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;

+ 5 - 0
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;

+ 5 - 2
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;

+ 3 - 1
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)
             {

+ 5 - 2
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.

+ 13 - 0
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
+    }
+}

+ 1 - 0
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" />

+ 23 - 0
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -122,6 +122,17 @@ namespace MediaBrowser.Controller.Providers
                             item.DisplayMediaType = type;
                         }
 
+                        break;
+                    }
+                case "CriticRating":
+                    {
+                        var text = reader.ReadElementContentAsString();
+                        float value;
+                        if (float.TryParse(text, NumberStyles.Any, _usCulture, out value))
+                        {
+                            item.CriticRating = value;
+                        }
+
                         break;
                     }
                 case "Budget":
@@ -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();

+ 5 - 0
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>

+ 2 - 4
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.

+ 5 - 0
MediaBrowser.Model/Querying/ItemFields.cs

@@ -26,6 +26,11 @@ namespace MediaBrowser.Model.Querying
         /// </summary>
         CriticRatingSummary,
 
+        /// <summary>
+        /// The custom rating
+        /// </summary>
+        CustomRating,
+        
         /// <summary>
         /// The date created of the item
         /// </summary>

+ 9 - 0
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>

+ 9 - 0
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>

+ 10 - 4
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>

+ 9 - 0
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>

+ 9 - 0
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>

+ 1 - 1
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);
         }

+ 12 - 2
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>

+ 9 - 0
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))

+ 9 - 0
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>

+ 9 - 0
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>

+ 1 - 0
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>");
 

+ 73 - 33
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>

+ 9 - 0
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>

+ 9 - 0
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>

+ 9 - 0
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>

+ 8 - 0
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>

+ 8 - 0
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>

+ 8 - 0
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>

+ 61 - 28
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();
                 }
             }
         }

+ 27 - 70
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
             {

+ 1 - 1
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
                 {

+ 4 - 4
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>