Sfoglia il codice sorgente

Only fire metadata savers when appropriate

Luke Pulverenti 12 anni fa
parent
commit
92cd71143d
33 ha cambiato i file con 385 aggiunte e 160 eliminazioni
  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>