浏览代码

convert games to new providers

Luke Pulverenti 11 年之前
父节点
当前提交
9e0c1340fc
共有 83 个文件被更改,包括 1353 次插入965 次删除
  1. 110 0
      MediaBrowser.Api/ConfigurationService.cs
  2. 1 0
      MediaBrowser.Api/MediaBrowser.Api.csproj
  3. 1 1
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  4. 2 86
      MediaBrowser.Api/SystemService.cs
  5. 0 85
      MediaBrowser.Controller/Entities/BaseItem.cs
  6. 0 7
      MediaBrowser.Controller/Entities/IHasImages.cs
  7. 0 7
      MediaBrowser.Controller/Entities/IHasScreenshots.cs
  8. 1 11
      MediaBrowser.Controller/Library/ILibraryManager.cs
  9. 10 4
      MediaBrowser.Controller/Library/IMetadataSaver.cs
  10. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  11. 15 0
      MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
  12. 6 0
      MediaBrowser.Controller/Providers/IHasMetadata.cs
  13. 3 10
      MediaBrowser.Controller/Providers/IMetadataService.cs
  14. 18 1
      MediaBrowser.Controller/Providers/IProviderManager.cs
  15. 18 0
      MediaBrowser.Controller/Providers/ItemId.cs
  16. 9 3
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  17. 9 3
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  18. 36 0
      MediaBrowser.Model/Configuration/AutoOrganize.cs
  19. 0 81
      MediaBrowser.Model/Configuration/ImageDownloadOptions.cs
  20. 89 0
      MediaBrowser.Model/Configuration/MetadataOptions.cs
  21. 60 0
      MediaBrowser.Model/Configuration/MetadataPlugin.cs
  22. 15 84
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  23. 3 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  24. 4 4
      MediaBrowser.Providers/BaseXmlProvider.cs
  25. 8 4
      MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
  26. 4 4
      MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs
  27. 1 1
      MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs
  28. 7 4
      MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs
  29. 59 0
      MediaBrowser.Providers/Games/GameMetadataService.cs
  30. 0 93
      MediaBrowser.Providers/Games/GameProviderFromXml.cs
  31. 59 0
      MediaBrowser.Providers/Games/GameSystemMetadataService.cs
  32. 0 92
      MediaBrowser.Providers/Games/GameSystemProviderFromXml.cs
  33. 63 0
      MediaBrowser.Providers/Games/GameSystemXmlParser.cs
  34. 59 0
      MediaBrowser.Providers/Games/GameSystemXmlProvider.cs
  35. 2 8
      MediaBrowser.Providers/Games/GameXmlParser.cs
  36. 74 0
      MediaBrowser.Providers/Games/GameXmlProvider.cs
  37. 1 1
      MediaBrowser.Providers/Genres/GenreImageProvider.cs
  38. 7 4
      MediaBrowser.Providers/Genres/GenreMetadataService.cs
  39. 7 4
      MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs
  40. 4 4
      MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs
  41. 7 4
      MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
  42. 0 21
      MediaBrowser.Providers/Manager/ConcreteMetadataService.cs
  43. 0 9
      MediaBrowser.Providers/Manager/ImageSaver.cs
  44. 38 53
      MediaBrowser.Providers/Manager/ItemImageProvider.cs
  45. 56 28
      MediaBrowser.Providers/Manager/MetadataService.cs
  46. 162 6
      MediaBrowser.Providers/Manager/ProviderManager.cs
  47. 5 3
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  48. 12 8
      MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
  49. 15 13
      MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
  50. 1 1
      MediaBrowser.Providers/Movies/MovieDbProvider.cs
  51. 4 4
      MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
  52. 6 4
      MediaBrowser.Providers/Music/AlbumMetadataService.cs
  53. 4 4
      MediaBrowser.Providers/Music/AlbumXmlProvider.cs
  54. 6 4
      MediaBrowser.Providers/Music/ArtistMetadataService.cs
  55. 4 4
      MediaBrowser.Providers/Music/ArtistXmlProvider.cs
  56. 1 1
      MediaBrowser.Providers/MusicGenres/MusicGenreImageProvider.cs
  57. 6 4
      MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
  58. 7 4
      MediaBrowser.Providers/People/PersonMetadataService.cs
  59. 4 4
      MediaBrowser.Providers/People/PersonXmlProvider.cs
  60. 13 7
      MediaBrowser.Providers/Savers/AlbumXmlSaver.cs
  61. 13 4
      MediaBrowser.Providers/Savers/ArtistXmlSaver.cs
  62. 13 4
      MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs
  63. 14 8
      MediaBrowser.Providers/Savers/ChannelXmlSaver.cs
  64. 13 5
      MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs
  65. 18 6
      MediaBrowser.Providers/Savers/FolderXmlSaver.cs
  66. 21 6
      MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs
  67. 15 8
      MediaBrowser.Providers/Savers/GameXmlSaver.cs
  68. 23 14
      MediaBrowser.Providers/Savers/MovieXmlSaver.cs
  69. 15 8
      MediaBrowser.Providers/Savers/PersonXmlSaver.cs
  70. 13 5
      MediaBrowser.Providers/Savers/SeasonXmlSaver.cs
  71. 15 7
      MediaBrowser.Providers/Savers/SeriesXmlSaver.cs
  72. 7 4
      MediaBrowser.Providers/Studios/StudioMetadataService.cs
  73. 1 1
      MediaBrowser.Providers/Studios/StudiosImageProvider.cs
  74. 4 1
      MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
  75. 10 7
      MediaBrowser.Providers/TV/FanArtTVProvider.cs
  76. 8 11
      MediaBrowser.Providers/TV/TvdbSeasonProvider.cs
  77. 9 9
      MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
  78. 8 4
      MediaBrowser.Providers/Users/UserMetadataService.cs
  79. 7 51
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  80. 4 4
      MediaBrowser.ServerApplication/ApplicationHost.cs
  81. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  82. 1 1
      Nuget/MediaBrowser.Common.nuspec
  83. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 110 - 0
MediaBrowser.Api/ConfigurationService.cs

@@ -0,0 +1,110 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Serialization;
+using ServiceStack;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api
+{
+    /// <summary>
+    /// Class GetConfiguration
+    /// </summary>
+    [Route("/System/Configuration", "GET")]
+    [Api(("Gets application configuration"))]
+    public class GetConfiguration : IReturn<ServerConfiguration>
+    {
+
+    }
+
+    /// <summary>
+    /// Class UpdateConfiguration
+    /// </summary>
+    [Route("/System/Configuration", "POST")]
+    [Api(("Updates application configuration"))]
+    public class UpdateConfiguration : ServerConfiguration, IReturnVoid
+    {
+    }
+
+    [Route("/System/Configuration/MetadataOptions/Default", "GET")]
+    [Api(("Gets a default MetadataOptions object"))]
+    public class GetDefaultMetadataOptions : IReturn<MetadataOptions>
+    {
+
+    }
+
+    [Route("/System/Configuration/MetadataPlugins", "GET")]
+    [Api(("Gets all available metadata plugins"))]
+    public class GetMetadataPlugins : IReturn<List<MetadataPluginSummary>>
+    {
+
+    }
+
+    public class ConfigurationService : BaseApiService
+    {
+        /// <summary>
+        /// The _json serializer
+        /// </summary>
+        private readonly IJsonSerializer _jsonSerializer;
+
+        /// <summary>
+        /// The _configuration manager
+        /// </summary>
+        private readonly IServerConfigurationManager _configurationManager;
+
+        private readonly IFileSystem _fileSystem;
+        private readonly IProviderManager _providerManager;
+
+        public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager)
+        {
+            _jsonSerializer = jsonSerializer;
+            _configurationManager = configurationManager;
+            _fileSystem = fileSystem;
+            _providerManager = providerManager;
+        }
+
+        /// <summary>
+        /// Gets the specified request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>System.Object.</returns>
+        public object Get(GetConfiguration request)
+        {
+            var configPath = _configurationManager.ApplicationPaths.SystemConfigurationFilePath;
+
+            var dateModified = _fileSystem.GetLastWriteTimeUtc(configPath);
+
+            var cacheKey = (configPath + dateModified.Ticks).GetMD5();
+
+            return ToOptimizedResultUsingCache(cacheKey, dateModified, null, () => _configurationManager.Configuration);
+        }
+
+        /// <summary>
+        /// Posts the specified configuraiton.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        public void Post(UpdateConfiguration request)
+        {
+            // Silly, but we need to serialize and deserialize or the XmlSerializer will write the xml with an element name of UpdateConfiguration
+
+            var json = _jsonSerializer.SerializeToString(request);
+
+            var config = _jsonSerializer.DeserializeFromString<ServerConfiguration>(json);
+
+            _configurationManager.ReplaceConfiguration(config);
+        }
+
+        public object Get(GetDefaultMetadataOptions request)
+        {
+            return ToOptimizedResult(new MetadataOptions());
+        }
+
+        public object Get(GetMetadataPlugins request)
+        {
+            return ToOptimizedResult(_providerManager.GetAllMetadataPlugins().ToList());
+        }
+    }
+}

+ 1 - 0
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -72,6 +72,7 @@
     </Compile>
     </Compile>
     <Compile Include="AlbumsService.cs" />
     <Compile Include="AlbumsService.cs" />
     <Compile Include="BaseApiService.cs" />
     <Compile Include="BaseApiService.cs" />
+    <Compile Include="ConfigurationService.cs" />
     <Compile Include="DefaultTheme\DefaultThemeService.cs" />
     <Compile Include="DefaultTheme\DefaultThemeService.cs" />
     <Compile Include="DefaultTheme\Models.cs" />
     <Compile Include="DefaultTheme\Models.cs" />
     <Compile Include="DisplayPreferencesService.cs" />
     <Compile Include="DisplayPreferencesService.cs" />

+ 1 - 1
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -161,7 +161,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
 
             if (!string.IsNullOrEmpty(contentFeatures))
             if (!string.IsNullOrEmpty(contentFeatures))
             {
             {
-                responseHeaders["ContentFeatures.DLNA.ORG"] = (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
+                responseHeaders["contentFeatures.dlna.org"] = (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
             }
             }
 
 
             foreach (var item in responseHeaders)
             foreach (var item in responseHeaders)

+ 2 - 86
MediaBrowser.Api/SystemService.cs

@@ -1,14 +1,6 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.IO;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Serialization;
+using MediaBrowser.Controller;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
 using ServiceStack;
 using ServiceStack;
-using System;
-using System.IO;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
@@ -38,71 +30,25 @@ namespace MediaBrowser.Api
     {
     {
     }
     }
     
     
-    /// <summary>
-    /// Class GetConfiguration
-    /// </summary>
-    [Route("/System/Configuration", "GET")]
-    [Api(("Gets application configuration"))]
-    public class GetConfiguration : IReturn<ServerConfiguration>
-    {
-
-    }
-
-    /// <summary>
-    /// Class UpdateConfiguration
-    /// </summary>
-    [Route("/System/Configuration", "POST")]
-    [Api(("Updates application configuration"))]
-    public class UpdateConfiguration : ServerConfiguration, IReturnVoid
-    {
-    }
-
     /// <summary>
     /// <summary>
     /// Class SystemInfoService
     /// Class SystemInfoService
     /// </summary>
     /// </summary>
     public class SystemService : BaseApiService
     public class SystemService : BaseApiService
     {
     {
-        /// <summary>
-        /// The _json serializer
-        /// </summary>
-        private readonly IJsonSerializer _jsonSerializer;
-
         /// <summary>
         /// <summary>
         /// The _app host
         /// The _app host
         /// </summary>
         /// </summary>
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
 
 
-        /// <summary>
-        /// The _configuration manager
-        /// </summary>
-        private readonly IServerConfigurationManager _configurationManager;
-
-        private readonly IFileSystem _fileSystem;
-
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="SystemService" /> class.
         /// Initializes a new instance of the <see cref="SystemService" /> class.
         /// </summary>
         /// </summary>
-        /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="appHost">The app host.</param>
         /// <param name="appHost">The app host.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
         /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
         /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
-        public SystemService(IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
-            : base()
+        public SystemService(IServerApplicationHost appHost)
         {
         {
-            if (jsonSerializer == null)
-            {
-                throw new ArgumentNullException("jsonSerializer");
-            }
-            if (appHost == null)
-            {
-                throw new ArgumentNullException("appHost");
-            }
-
             _appHost = appHost;
             _appHost = appHost;
-            _configurationManager = configurationManager;
-            _fileSystem = fileSystem;
-            _jsonSerializer = jsonSerializer;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -117,22 +63,6 @@ namespace MediaBrowser.Api
             return ToOptimizedResult(result);
             return ToOptimizedResult(result);
         }
         }
 
 
-        /// <summary>
-        /// Gets the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Object.</returns>
-        public object Get(GetConfiguration request)
-        {
-            var configPath = _configurationManager.ApplicationPaths.SystemConfigurationFilePath;
-
-            var dateModified = _fileSystem.GetLastWriteTimeUtc(configPath);
-
-            var cacheKey = (configPath + dateModified.Ticks).GetMD5();
-
-            return ToOptimizedResultUsingCache(cacheKey, dateModified, null, () => _configurationManager.Configuration);
-        }
-
         /// <summary>
         /// <summary>
         /// Posts the specified request.
         /// Posts the specified request.
         /// </summary>
         /// </summary>
@@ -159,19 +89,5 @@ namespace MediaBrowser.Api
             });
             });
         }
         }
 
 
-        /// <summary>
-        /// Posts the specified configuraiton.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public void Post(UpdateConfiguration request)
-        {
-            // Silly, but we need to serialize and deserialize or the XmlSerializer will write the xml with an element name of UpdateConfiguration
-
-            var json = _jsonSerializer.SerializeToString(request);
-
-            var config = _jsonSerializer.DeserializeFromString<ServerConfiguration>(json);
-
-            _configurationManager.ReplaceConfiguration(config);
-        }
     }
     }
 }
 }

+ 0 - 85
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -34,7 +34,6 @@ namespace MediaBrowser.Controller.Entities
             Images = new Dictionary<ImageType, string>();
             Images = new Dictionary<ImageType, string>();
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
             LockedFields = new List<MetadataFields>();
             LockedFields = new List<MetadataFields>();
-            ImageSources = new List<ImageSourceInfo>();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -474,12 +473,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The backdrop image paths.</value>
         /// <value>The backdrop image paths.</value>
         public List<string> BackdropImagePaths { get; set; }
         public List<string> BackdropImagePaths { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the backdrop image sources.
-        /// </summary>
-        /// <value>The backdrop image sources.</value>
-        public List<ImageSourceInfo> ImageSources { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the official rating.
         /// Gets or sets the official rating.
         /// </summary>
         /// </summary>
@@ -1458,8 +1451,6 @@ namespace MediaBrowser.Controller.Entities
 
 
                 BackdropImagePaths.Remove(file);
                 BackdropImagePaths.Remove(file);
 
 
-                RemoveImageSourceForPath(file);
-
                 // Delete the source file
                 // Delete the source file
                 DeleteImagePath(file);
                 DeleteImagePath(file);
             }
             }
@@ -1567,88 +1558,12 @@ namespace MediaBrowser.Controller.Entities
             {
             {
                 BackdropImagePaths.Remove(path);
                 BackdropImagePaths.Remove(path);
 
 
-                RemoveImageSourceForPath(path);
-
                 changed = true;
                 changed = true;
             }
             }
 
 
             return changed;
             return changed;
         }
         }
 
 
-        /// <summary>
-        /// Adds the image source.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="url">The URL.</param>
-        public void AddImageSource(string path, string url)
-        {
-            RemoveImageSourceForPath(path);
-
-            var pathMd5 = path.ToLower().GetMD5();
-
-            ImageSources.Add(new ImageSourceInfo
-            {
-                ImagePathMD5 = pathMd5,
-                ImageUrlMD5 = url.ToLower().GetMD5()
-            });
-        }
-
-        /// <summary>
-        /// Gets the image source info.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>ImageSourceInfo.</returns>
-        public ImageSourceInfo GetImageSourceInfo(string path)
-        {
-            if (ImageSources.Count == 0)
-            {
-                return null;
-            }
-
-            var pathMd5 = path.ToLower().GetMD5();
-
-            return ImageSources.FirstOrDefault(i => i.ImagePathMD5 == pathMd5);
-        }
-
-        /// <summary>
-        /// Removes the image source for path.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        public void RemoveImageSourceForPath(string path)
-        {
-            if (ImageSources.Count == 0)
-            {
-                return;
-            }
-
-            var pathMd5 = path.ToLower().GetMD5();
-
-            // Remove existing
-            foreach (var entry in ImageSources
-                .Where(i => i.ImagePathMD5 == pathMd5)
-                .ToList())
-            {
-                ImageSources.Remove(entry);
-            }
-        }
-
-        /// <summary>
-        /// Determines whether [contains image with source URL] [the specified URL].
-        /// </summary>
-        /// <param name="url">The URL.</param>
-        /// <returns><c>true</c> if [contains image with source URL] [the specified URL]; otherwise, <c>false</c>.</returns>
-        public bool ContainsImageWithSourceUrl(string url)
-        {
-            if (ImageSources.Count == 0)
-            {
-                return false;
-            }
-
-            var md5 = url.ToLower().GetMD5();
-
-            return ImageSources.Any(i => i.ImageUrlMD5 == md5);
-        }
-
         /// <summary>
         /// <summary>
         /// Validates the screenshots.
         /// Validates the screenshots.
         /// </summary>
         /// </summary>

+ 0 - 7
MediaBrowser.Controller/Entities/IHasImages.cs

@@ -99,13 +99,6 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// </summary>
         /// <value>The backdrop image paths.</value>
         /// <value>The backdrop image paths.</value>
         List<string> BackdropImagePaths { get; set; }
         List<string> BackdropImagePaths { get; set; }
-
-        /// <summary>
-        /// Determines whether [contains image with source URL] [the specified URL].
-        /// </summary>
-        /// <param name="url">The URL.</param>
-        /// <returns><c>true</c> if [contains image with source URL] [the specified URL]; otherwise, <c>false</c>.</returns>
-        bool ContainsImageWithSourceUrl(string url);
     }
     }
 
 
     public static class HasImagesExtensions
     public static class HasImagesExtensions

+ 0 - 7
MediaBrowser.Controller/Entities/IHasScreenshots.cs

@@ -12,12 +12,5 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// </summary>
         /// <value>The screenshot image paths.</value>
         /// <value>The screenshot image paths.</value>
         List<string> ScreenshotImagePaths { get; set; }
         List<string> ScreenshotImagePaths { get; set; }
-
-        /// <summary>
-        /// Determines whether [contains image with source URL] [the specified URL].
-        /// </summary>
-        /// <param name="url">The URL.</param>
-        /// <returns><c>true</c> if [contains image with source URL] [the specified URL]; otherwise, <c>false</c>.</returns>
-        bool ContainsImageWithSourceUrl(string url);
     }
     }
 }
 }

+ 1 - 11
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -165,7 +165,6 @@ namespace MediaBrowser.Controller.Library
         /// <param name="prescanTasks">The prescan tasks.</param>
         /// <param name="prescanTasks">The prescan tasks.</param>
         /// <param name="postscanTasks">The postscan tasks.</param>
         /// <param name="postscanTasks">The postscan tasks.</param>
         /// <param name="peoplePrescanTasks">The people prescan tasks.</param>
         /// <param name="peoplePrescanTasks">The people prescan tasks.</param>
-        /// <param name="savers">The savers.</param>
         void AddParts(IEnumerable<IResolverIgnoreRule> rules,
         void AddParts(IEnumerable<IResolverIgnoreRule> rules,
             IEnumerable<IVirtualFolderCreator> pluginFolders,
             IEnumerable<IVirtualFolderCreator> pluginFolders,
             IEnumerable<IItemResolver> resolvers,
             IEnumerable<IItemResolver> resolvers,
@@ -173,8 +172,7 @@ namespace MediaBrowser.Controller.Library
             IEnumerable<IBaseItemComparer> itemComparers,
             IEnumerable<IBaseItemComparer> itemComparers,
             IEnumerable<ILibraryPrescanTask> prescanTasks,
             IEnumerable<ILibraryPrescanTask> prescanTasks,
             IEnumerable<ILibraryPostScanTask> postscanTasks,
             IEnumerable<ILibraryPostScanTask> postscanTasks,
-            IEnumerable<IPeoplePrescanTask> peoplePrescanTasks,
-            IEnumerable<IMetadataSaver> savers);
+            IEnumerable<IPeoplePrescanTask> peoplePrescanTasks);
 
 
         /// <summary>
         /// <summary>
         /// Sorts the specified items.
         /// Sorts the specified items.
@@ -300,14 +298,6 @@ namespace MediaBrowser.Controller.Library
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
         string FindCollectionType(BaseItem item);
         string FindCollectionType(BaseItem item);
 
 
-        /// <summary>
-        /// Saves the metadata.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="updateType">Type of the update.</param>
-        /// <returns>Task.</returns>
-        Task SaveMetadata(BaseItem item, ItemUpdateType updateType);
-
         /// <summary>
         /// <summary>
         /// Gets all artists.
         /// Gets all artists.
         /// </summary>
         /// </summary>

+ 10 - 4
MediaBrowser.Controller/Library/IMetadataSaver.cs

@@ -1,4 +1,4 @@
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
 using System.Threading;
 using System.Threading;
 
 
 namespace MediaBrowser.Controller.Library
 namespace MediaBrowser.Controller.Library
@@ -8,20 +8,26 @@ namespace MediaBrowser.Controller.Library
     /// </summary>
     /// </summary>
     public interface IMetadataSaver
     public interface IMetadataSaver
     {
     {
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        string Name { get; }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        bool IsEnabledFor(BaseItem item, ItemUpdateType updateType);
+        bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType);
 
 
         /// <summary>
         /// <summary>
         /// Gets the save path.
         /// Gets the save path.
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        string GetSavePath(BaseItem item);
+        string GetSavePath(IHasMetadata item);
 
 
         /// <summary>
         /// <summary>
         /// Saves the specified item.
         /// Saves the specified item.
@@ -29,6 +35,6 @@ namespace MediaBrowser.Controller.Library
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        void Save(BaseItem item, CancellationToken cancellationToken);
+        void Save(IHasMetadata item, CancellationToken cancellationToken);
     }
     }
 }
 }

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -143,6 +143,7 @@
     <Compile Include="Notifications\NotificationUpdateEventArgs.cs" />
     <Compile Include="Notifications\NotificationUpdateEventArgs.cs" />
     <Compile Include="Persistence\IFileOrganizationRepository.cs" />
     <Compile Include="Persistence\IFileOrganizationRepository.cs" />
     <Compile Include="Persistence\MediaStreamQuery.cs" />
     <Compile Include="Persistence\MediaStreamQuery.cs" />
+    <Compile Include="Providers\ICustomMetadataProvider.cs" />
     <Compile Include="Providers\IDynamicInfoProvider.cs" />
     <Compile Include="Providers\IDynamicInfoProvider.cs" />
     <Compile Include="Providers\IHasMetadata.cs" />
     <Compile Include="Providers\IHasMetadata.cs" />
     <Compile Include="Providers\IImageProvider.cs" />
     <Compile Include="Providers\IImageProvider.cs" />

+ 15 - 0
MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs

@@ -0,0 +1,15 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public interface ICustomMetadataProvider : IMetadataProvider
+    {
+    }
+
+    public interface ICustomMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ICustomMetadataProvider
+        where TItemType : IHasMetadata
+    {
+        Task FetchAsync(TItemType item, CancellationToken cancellationToken);
+    }
+}

+ 6 - 0
MediaBrowser.Controller/Providers/IHasMetadata.cs

@@ -27,5 +27,11 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         /// </summary>
         /// <value>The date last saved.</value>
         /// <value>The date last saved.</value>
         DateTime DateLastSaved { get; set; }
         DateTime DateLastSaved { get; set; }
+
+        /// <summary>
+        /// Determines whether [is save local metadata enabled].
+        /// </summary>
+        /// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns>
+        bool IsSaveLocalMetadataEnabled();
     }
     }
 }
 }

+ 3 - 10
MediaBrowser.Controller/Providers/IMetadataService.cs

@@ -1,17 +1,10 @@
-using System.Collections.Generic;
-using System.Threading;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Controller.Providers
 namespace MediaBrowser.Controller.Providers
 {
 {
     public interface IMetadataService
     public interface IMetadataService
     {
     {
-        /// <summary>
-        /// Adds the parts.
-        /// </summary>
-        /// <param name="providers">The providers.</param>
-        void AddParts(IEnumerable<IMetadataProvider> providers);
-
         /// <summary>
         /// <summary>
         /// Determines whether this instance can refresh the specified item.
         /// Determines whether this instance can refresh the specified item.
         /// </summary>
         /// </summary>
@@ -23,10 +16,10 @@ namespace MediaBrowser.Controller.Providers
         /// Refreshes the metadata.
         /// Refreshes the metadata.
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
-        /// <param name="options">The options.</param>
+        /// <param name="refreshOptions">The options.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken);
+        Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Gets the order.
         /// Gets the order.

+ 18 - 1
MediaBrowser.Controller/Providers/IProviderManager.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -64,7 +65,9 @@ namespace MediaBrowser.Controller.Providers
         /// <param name="imageProviders">The image providers.</param>
         /// <param name="imageProviders">The image providers.</param>
         /// <param name="metadataServices">The metadata services.</param>
         /// <param name="metadataServices">The metadata services.</param>
         /// <param name="metadataProviders">The metadata providers.</param>
         /// <param name="metadataProviders">The metadata providers.</param>
-        void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders);
+        /// <param name="savers">The savers.</param>
+        void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
+            IEnumerable<IMetadataSaver> savers);
 
 
         /// <summary>
         /// <summary>
         /// Gets the available remote images.
         /// Gets the available remote images.
@@ -82,5 +85,19 @@ namespace MediaBrowser.Controller.Providers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>IEnumerable{ImageProviderInfo}.</returns>
         /// <returns>IEnumerable{ImageProviderInfo}.</returns>
         IEnumerable<ImageProviderInfo> GetImageProviderInfo(IHasImages item);
         IEnumerable<ImageProviderInfo> GetImageProviderInfo(IHasImages item);
+
+        /// <summary>
+        /// Gets all metadata plugins.
+        /// </summary>
+        /// <returns>IEnumerable{MetadataPlugin}.</returns>
+        IEnumerable<MetadataPluginSummary> GetAllMetadataPlugins();
+
+        /// <summary>
+        /// Saves the metadata.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="updateType">Type of the update.</param>
+        /// <returns>Task.</returns>
+        Task SaveMetadata(IHasMetadata item, ItemUpdateType updateType);
     }
     }
 }
 }

+ 18 - 0
MediaBrowser.Controller/Providers/ItemId.cs

@@ -51,4 +51,22 @@ namespace MediaBrowser.Controller.Providers
         /// <value>The artist music brainz identifier.</value>
         /// <value>The artist music brainz identifier.</value>
         public string ArtistMusicBrainzId { get; set; }
         public string ArtistMusicBrainzId { get; set; }
     }
     }
+
+    public class GameId : ItemId
+    {
+        /// <summary>
+        /// Gets or sets the game system.
+        /// </summary>
+        /// <value>The game system.</value>
+        public string GameSystem { get; set; }
+    }
+
+    public class GameSystemId : ItemId
+    {
+        /// <summary>
+        /// Gets or sets the path.
+        /// </summary>
+        /// <value>The path.</value>
+        public string Path { get; set; }
+    }
 }
 }

+ 9 - 3
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -74,15 +74,21 @@
     <Compile Include="..\MediaBrowser.Model\ApiClient\ServerEventArgs.cs">
     <Compile Include="..\MediaBrowser.Model\ApiClient\ServerEventArgs.cs">
       <Link>ApiClient\ServerEventArgs.cs</Link>
       <Link>ApiClient\ServerEventArgs.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\AutoOrganize.cs">
+      <Link>Configuration\AutoOrganize.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
     </Compile>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\Configuration\ImageDownloadOptions.cs">
-      <Link>Configuration\ImageDownloadOptions.cs</Link>
-    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs">
       <Link>Configuration\ManualLoginCategory.cs</Link>
       <Link>Configuration\ManualLoginCategory.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\MetadataOptions.cs">
+      <Link>Configuration\MetadataOptions.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\MetadataPlugin.cs">
+      <Link>Configuration\MetadataPlugin.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\ServerConfiguration.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\ServerConfiguration.cs">
       <Link>Configuration\ServerConfiguration.cs</Link>
       <Link>Configuration\ServerConfiguration.cs</Link>
     </Compile>
     </Compile>

+ 9 - 3
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -61,15 +61,21 @@
     <Compile Include="..\MediaBrowser.Model\ApiClient\ServerEventArgs.cs">
     <Compile Include="..\MediaBrowser.Model\ApiClient\ServerEventArgs.cs">
       <Link>ApiClient\ServerEventArgs.cs</Link>
       <Link>ApiClient\ServerEventArgs.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\AutoOrganize.cs">
+      <Link>Configuration\AutoOrganize.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
     </Compile>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\Configuration\ImageDownloadOptions.cs">
-      <Link>Configuration\ImageDownloadOptions.cs</Link>
-    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs">
       <Link>Configuration\ManualLoginCategory.cs</Link>
       <Link>Configuration\ManualLoginCategory.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\MetadataOptions.cs">
+      <Link>Configuration\MetadataOptions.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\MetadataPlugin.cs">
+      <Link>Configuration\MetadataPlugin.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\ServerConfiguration.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\ServerConfiguration.cs">
       <Link>Configuration\ServerConfiguration.cs</Link>
       <Link>Configuration\ServerConfiguration.cs</Link>
     </Compile>
     </Compile>

+ 36 - 0
MediaBrowser.Model/Configuration/AutoOrganize.cs

@@ -0,0 +1,36 @@
+
+namespace MediaBrowser.Model.Configuration
+{
+    public class TvFileOrganizationOptions
+    {
+        public bool IsEnabled { get; set; }
+        public int MinFileSizeMb { get; set; }
+        public string[] LeftOverFileExtensionsToDelete { get; set; }
+        public string[] WatchLocations { get; set; }
+
+        public string SeasonFolderPattern { get; set; }
+
+        public string SeasonZeroFolderName { get; set; }
+
+        public string EpisodeNamePattern { get; set; }
+        public string MultiEpisodeNamePattern { get; set; }
+
+        public bool OverwriteExistingEpisodes { get; set; }
+
+        public bool DeleteEmptyFolders { get; set; }
+
+        public TvFileOrganizationOptions()
+        {
+            MinFileSizeMb = 50;
+
+            LeftOverFileExtensionsToDelete = new string[] { };
+
+            WatchLocations = new string[] { };
+
+            EpisodeNamePattern = "%sn - %sx%0e - %en.%ext";
+            MultiEpisodeNamePattern = "%sn - %sx%0e-x%0ed - %en.%ext";
+            SeasonFolderPattern = "Season %s";
+            SeasonZeroFolderName = "Season 0";
+        }
+    }
+}

+ 0 - 81
MediaBrowser.Model/Configuration/ImageDownloadOptions.cs

@@ -1,81 +0,0 @@
-
-namespace MediaBrowser.Model.Configuration
-{
-    /// <summary>
-    /// Class ImageDownloadOptions
-    /// </summary>
-    public class ImageDownloadOptions
-    {
-        /// <summary>
-        /// Download Art Image
-        /// </summary>
-        /// <value><c>true</c> if art; otherwise, <c>false</c>.</value>
-        public bool Art { get; set; }
-
-        /// <summary>
-        /// Download Logo Image
-        /// </summary>
-        /// <value><c>true</c> if logo; otherwise, <c>false</c>.</value>
-        public bool Logo { get; set; }
-
-        /// <summary>
-        /// Download Primary Image
-        /// </summary>
-        /// <value><c>true</c> if primary; otherwise, <c>false</c>.</value>
-        public bool Primary { get; set; }
-
-        /// <summary>
-        /// Download Backdrop Images
-        /// </summary>
-        /// <value><c>true</c> if backdrops; otherwise, <c>false</c>.</value>
-        public bool Backdrops { get; set; }
-
-        /// <summary>
-        /// Download Disc Image
-        /// </summary>
-        /// <value><c>true</c> if disc; otherwise, <c>false</c>.</value>
-        public bool Disc { get; set; }
-
-        /// <summary>
-        /// Download Thumb Image
-        /// </summary>
-        /// <value><c>true</c> if thumb; otherwise, <c>false</c>.</value>
-        public bool Thumb { get; set; }
-
-        /// <summary>
-        /// Download Banner Image
-        /// </summary>
-        /// <value><c>true</c> if banner; otherwise, <c>false</c>.</value>
-        public bool Banner { get; set; }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ImageDownloadOptions"/> class.
-        /// </summary>
-        public ImageDownloadOptions()
-        {
-            Art = true;
-            Logo = true;
-            Primary = true;
-            Backdrops = true;
-            Disc = true;
-            Thumb = true;
-            Banner = true;
-        }
-    }
-
-    /// <summary>
-    /// Class MetadataOptions.
-    /// </summary>
-    public class MetadataOptions
-    {
-        public int MaxBackdrops { get; set; }
-
-        public int MinBackdropWidth { get; set; }
-
-        public MetadataOptions()
-        {
-            MaxBackdrops = 3;
-            MinBackdropWidth = 1280;
-        }
-    }
-}

+ 89 - 0
MediaBrowser.Model/Configuration/MetadataOptions.cs

@@ -0,0 +1,89 @@
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Model.Configuration
+{
+    /// <summary>
+    /// Class MetadataOptions.
+    /// </summary>
+    public class MetadataOptions
+    {
+        public string ItemType { get; set; }
+
+        public ImageOption[] ImageOptions { get; set; }
+
+        public string[] DisabledMetadataSavers { get; set; }
+
+        public MetadataOptions()
+            : this(3, 1280)
+        {
+        }
+
+        public MetadataOptions(int backdropLimit, int minBackdropWidth)
+        {
+            var imageOptions = new List<ImageOption>
+            {
+                new ImageOption
+                {
+                    Limit = backdropLimit,
+                    MinWidth = minBackdropWidth,
+                    Type = ImageType.Backdrop
+                }
+            };
+
+            ImageOptions = imageOptions.ToArray();
+            DisabledMetadataSavers = new string[] { };
+        }
+
+        public int GetLimit(ImageType type)
+        {
+            var option = ImageOptions.FirstOrDefault(i => i.Type == type);
+
+            return option == null ? 1 : option.Limit;
+        }
+
+        public int GetMinWidth(ImageType type)
+        {
+            var option = ImageOptions.FirstOrDefault(i => i.Type == type);
+
+            return option == null ? 0 : option.MinWidth;
+        }
+
+        public bool IsEnabled(ImageType type)
+        {
+            return GetLimit(type) > 0;
+        }
+
+        public bool IsMetadataSaverEnabled(string name)
+        {
+            return !DisabledMetadataSavers.Contains(name, StringComparer.OrdinalIgnoreCase);
+        }
+    }
+
+    public class ImageOption
+    {
+        /// <summary>
+        /// Gets or sets the type.
+        /// </summary>
+        /// <value>The type.</value>
+        public ImageType Type { get; set; }
+        /// <summary>
+        /// Gets or sets the limit.
+        /// </summary>
+        /// <value>The limit.</value>
+        public int Limit { get; set; }
+
+        /// <summary>
+        /// Gets or sets the minimum width.
+        /// </summary>
+        /// <value>The minimum width.</value>
+        public int MinWidth { get; set; }
+
+        public ImageOption()
+        {
+            Limit = 1;
+        }
+    }
+}

+ 60 - 0
MediaBrowser.Model/Configuration/MetadataPlugin.cs

@@ -0,0 +1,60 @@
+using MediaBrowser.Model.Entities;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.Configuration
+{
+    public class MetadataPlugin
+    {
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the type.
+        /// </summary>
+        /// <value>The type.</value>
+        public MetadataPluginType Type { get; set; }
+    }
+
+    public class MetadataPluginSummary
+    {
+        /// <summary>
+        /// Gets or sets the type of the item.
+        /// </summary>
+        /// <value>The type of the item.</value>
+        public string ItemType { get; set; }
+
+        /// <summary>
+        /// Gets or sets the plugins.
+        /// </summary>
+        /// <value>The plugins.</value>
+        public List<MetadataPlugin> Plugins { get; set; }
+
+        /// <summary>
+        /// Gets or sets the supported image types.
+        /// </summary>
+        /// <value>The supported image types.</value>
+        public List<ImageType> SupportedImageTypes { get; set; }
+
+        public MetadataPluginSummary()
+        {
+            SupportedImageTypes = new List<ImageType>();
+            Plugins = new List<MetadataPlugin>();
+        }
+    }
+
+    /// <summary>
+    /// Enum MetadataPluginType
+    /// </summary>
+    public enum MetadataPluginType
+    {
+        LocalImageProvider,
+        ImageFetcher,
+        ImageSaver,
+        LocalMetadataProvider,
+        MetadataFetcher,
+        MetadataSaver
+    }
+}

+ 15 - 84
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 using MediaBrowser.Model.Weather;
 using MediaBrowser.Model.Weather;
 using System;
 using System;
 
 
@@ -87,31 +88,6 @@ namespace MediaBrowser.Model.Configuration
         /// <value>The metadata country code.</value>
         /// <value>The metadata country code.</value>
         public string MetadataCountryCode { get; set; }
         public string MetadataCountryCode { get; set; }
 
 
-        /// <summary>
-        /// Options for specific art to download for movies.
-        /// </summary>
-        public ImageDownloadOptions DownloadMovieImages { get; set; }
-
-        /// <summary>
-        /// Options for specific art to download for Series.
-        /// </summary>
-        public ImageDownloadOptions DownloadSeriesImages { get; set; }
-
-        /// <summary>
-        /// Options for specific art to download for Seasons.
-        /// </summary>
-        public ImageDownloadOptions DownloadSeasonImages { get; set; }
-
-        /// <summary>
-        /// Options for specific art to download for MusicArtists.
-        /// </summary>
-        public ImageDownloadOptions DownloadMusicArtistImages { get; set; }
-
-        /// <summary>
-        /// Options for specific art to download for MusicAlbums.
-        /// </summary>
-        public ImageDownloadOptions DownloadMusicAlbumImages { get; set; }
-
         /// <summary>
         /// <summary>
         /// Characters to be replaced with a ' ' in strings to create a sort name
         /// Characters to be replaced with a ' ' in strings to create a sort name
         /// </summary>
         /// </summary>
@@ -215,11 +191,7 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableEpisodeChapterImageExtraction { get; set; }
         public bool EnableEpisodeChapterImageExtraction { get; set; }
         public bool EnableOtherVideoChapterImageExtraction { get; set; }
         public bool EnableOtherVideoChapterImageExtraction { get; set; }
 
 
-        public MetadataOptions MovieOptions { get; set; }
-        public MetadataOptions TvOptions { get; set; }
-        public MetadataOptions MusicOptions { get; set; }
-        public MetadataOptions GameOptions { get; set; }
-        public MetadataOptions BookOptions { get; set; }
+        public MetadataOptions[] MetadataOptions { get; set; }
 
 
         public bool EnableDebugEncodingLogging { get; set; }
         public bool EnableDebugEncodingLogging { get; set; }
         public string TranscodingTempPath { get; set; }
         public string TranscodingTempPath { get; set; }
@@ -267,14 +239,6 @@ namespace MediaBrowser.Model.Configuration
             MetadataRefreshDays = 30;
             MetadataRefreshDays = 30;
             PreferredMetadataLanguage = "en";
             PreferredMetadataLanguage = "en";
             MetadataCountryCode = "US";
             MetadataCountryCode = "US";
-            DownloadMovieImages = new ImageDownloadOptions();
-            DownloadSeriesImages = new ImageDownloadOptions();
-            DownloadSeasonImages = new ImageDownloadOptions
-            {
-                Backdrops = false
-            };
-            DownloadMusicArtistImages = new ImageDownloadOptions();
-            DownloadMusicAlbumImages = new ImageDownloadOptions();
 
 
             SortReplaceCharacters = new[] { ".", "+", "%" };
             SortReplaceCharacters = new[] { ".", "+", "%" };
             SortRemoveCharacters = new[] { ",", "&", "-", "{", "}", "'" };
             SortRemoveCharacters = new[] { ",", "&", "-", "{", "}", "'" };
@@ -282,26 +246,26 @@ namespace MediaBrowser.Model.Configuration
 
 
             SeasonZeroDisplayName = "Specials";
             SeasonZeroDisplayName = "Specials";
 
 
-            MovieOptions = new MetadataOptions();
-            TvOptions = new MetadataOptions();
+            LiveTvOptions = new LiveTvOptions();
 
 
-            MusicOptions = new MetadataOptions()
-            {
-                MaxBackdrops = 1
-            };
+            TvFileOrganizationOptions = new TvFileOrganizationOptions();
 
 
-            GameOptions = new MetadataOptions();
+            EnableRealtimeMonitor = true;
 
 
-            BookOptions = new MetadataOptions
+            var options = new List<MetadataOptions>
             {
             {
-                MaxBackdrops = 1
+                new MetadataOptions(1, 1280) {ItemType = "Book"},
+                new MetadataOptions(1, 1280) {ItemType = "MusicAlbum"},
+                new MetadataOptions(1, 1280) {ItemType = "MusicArtist"},
+                new MetadataOptions(0, 1280) {ItemType = "Season"}
             };
             };
 
 
-            LiveTvOptions = new LiveTvOptions();
-
-            TvFileOrganizationOptions = new TvFileOrganizationOptions();
+            MetadataOptions = options.ToArray();
+        }
 
 
-            EnableRealtimeMonitor = true;
+        public MetadataOptions GetMetadataOptions(string type)
+        {
+            return MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase));
         }
         }
     }
     }
 
 
@@ -324,39 +288,6 @@ namespace MediaBrowser.Model.Configuration
         public int? GuideDays { get; set; }
         public int? GuideDays { get; set; }
     }
     }
 
 
-    public class TvFileOrganizationOptions
-    {
-        public bool IsEnabled { get; set; }
-        public int MinFileSizeMb { get; set; }
-        public string[] LeftOverFileExtensionsToDelete { get; set; }
-        public string[] WatchLocations { get; set; }
-
-        public string SeasonFolderPattern { get; set; }
-
-        public string SeasonZeroFolderName { get; set; }
-
-        public string EpisodeNamePattern { get; set; }
-        public string MultiEpisodeNamePattern { get; set; }
-
-        public bool OverwriteExistingEpisodes { get; set; }
-
-        public bool DeleteEmptyFolders { get; set; }
-
-        public TvFileOrganizationOptions()
-        {
-            MinFileSizeMb = 50;
-
-            LeftOverFileExtensionsToDelete = new string[] {};
-
-            WatchLocations = new string[] { };
-
-            EpisodeNamePattern = "%sn - %sx%0e - %en.%ext";
-            MultiEpisodeNamePattern = "%sn - %sx%0e-x%0ed - %en.%ext";
-            SeasonFolderPattern = "Season %s";
-            SeasonZeroFolderName = "Season 0";
-        }
-    }
-
     public class PathSubstitution
     public class PathSubstitution
     {
     {
         public string From { get; set; }
         public string From { get; set; }

+ 3 - 1
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -58,8 +58,11 @@
     <Compile Include="ApiClient\ApiClientExtensions.cs" />
     <Compile Include="ApiClient\ApiClientExtensions.cs" />
     <Compile Include="ApiClient\IServerEvents.cs" />
     <Compile Include="ApiClient\IServerEvents.cs" />
     <Compile Include="ApiClient\ServerEventArgs.cs" />
     <Compile Include="ApiClient\ServerEventArgs.cs" />
+    <Compile Include="Configuration\AutoOrganize.cs" />
     <Compile Include="Configuration\BaseApplicationConfiguration.cs" />
     <Compile Include="Configuration\BaseApplicationConfiguration.cs" />
     <Compile Include="Configuration\ManualLoginCategory.cs" />
     <Compile Include="Configuration\ManualLoginCategory.cs" />
+    <Compile Include="Configuration\MetadataPlugin.cs" />
+    <Compile Include="Configuration\MetadataOptions.cs" />
     <Compile Include="Configuration\ServerConfiguration.cs" />
     <Compile Include="Configuration\ServerConfiguration.cs" />
     <Compile Include="Drawing\ImageOutputFormat.cs" />
     <Compile Include="Drawing\ImageOutputFormat.cs" />
     <Compile Include="Dto\BaseItemPerson.cs" />
     <Compile Include="Dto\BaseItemPerson.cs" />
@@ -124,7 +127,6 @@
     <Compile Include="Session\MessageCommand.cs" />
     <Compile Include="Session\MessageCommand.cs" />
     <Compile Include="Session\PlayRequest.cs" />
     <Compile Include="Session\PlayRequest.cs" />
     <Compile Include="Session\PlaystateCommand.cs" />
     <Compile Include="Session\PlaystateCommand.cs" />
-    <Compile Include="Configuration\ImageDownloadOptions.cs" />
     <Compile Include="Logging\ILogManager.cs" />
     <Compile Include="Logging\ILogManager.cs" />
     <Compile Include="MediaInfo\BlurayDiscInfo.cs" />
     <Compile Include="MediaInfo\BlurayDiscInfo.cs" />
     <Compile Include="Entities\ChapterInfo.cs" />
     <Compile Include="Entities\ChapterInfo.cs" />

+ 4 - 4
MediaBrowser.Providers/BaseXmlProvider.cs

@@ -17,18 +17,18 @@ namespace MediaBrowser.Providers
             FileSystem = fileSystem;
             FileSystem = fileSystem;
         }
         }
 
 
-        protected abstract string GetXmlPath(string path);
+        protected abstract FileInfo GetXmlFile(string path);
 
 
         public bool HasChanged(IHasMetadata item, DateTime date)
         public bool HasChanged(IHasMetadata item, DateTime date)
         {
         {
-            var path = GetXmlPath(item.Path);
+            var file = GetXmlFile(item.Path);
 
 
-            return FileSystem.GetLastWriteTimeUtc(path) > date;
+            return FileSystem.GetLastWriteTimeUtc(file) > date;
         }
         }
 
 
         public bool HasLocalMetadata(IHasMetadata item)
         public bool HasLocalMetadata(IHasMetadata item)
         {
         {
-            return File.Exists(GetXmlPath(item.Path));
+            return GetXmlFile(item.Path).Exists;
         }
         }
     }
     }
 }
 }

+ 8 - 4
MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs

@@ -1,9 +1,12 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -15,13 +18,13 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.BoxSets
 namespace MediaBrowser.Providers.BoxSets
 {
 {
-    public class BoxSetMetadataService : ConcreteMetadataService<BoxSet, ItemId>
+    public class BoxSetMetadataService : MetadataService<BoxSet, ItemId>
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
         private readonly ILocalizationManager _iLocalizationManager;
         private readonly ILocalizationManager _iLocalizationManager;
 
 
-        public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager, ILocalizationManager iLocalizationManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager, ILocalizationManager iLocalizationManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
             _iLocalizationManager = iLocalizationManager;
             _iLocalizationManager = iLocalizationManager;
@@ -34,6 +37,7 @@ namespace MediaBrowser.Providers.BoxSets
         /// <param name="target">The target.</param>
         /// <param name="target">The target.</param>
         /// <param name="lockedFields">The locked fields.</param>
         /// <param name="lockedFields">The locked fields.</param>
         /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
         /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
         protected override void MergeData(BoxSet source, BoxSet target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         protected override void MergeData(BoxSet source, BoxSet target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         {
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);

+ 4 - 4
MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs

@@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.BoxSets
 
 
         public async Task<MetadataResult<BoxSet>> GetMetadata(string path, CancellationToken cancellationToken)
         public async Task<MetadataResult<BoxSet>> GetMetadata(string path, CancellationToken cancellationToken)
         {
         {
-            path = GetXmlPath(path);
+            path = GetXmlFile(path).FullName;
 
 
             var result = new MetadataResult<BoxSet>();
             var result = new MetadataResult<BoxSet>();
 
 
@@ -51,12 +51,12 @@ namespace MediaBrowser.Providers.BoxSets
 
 
         public string Name
         public string Name
         {
         {
-            get { return "Media Browser Xml"; }
+            get { return "Media Browser xml"; }
         }
         }
 
 
-        protected override string GetXmlPath(string path)
+        protected override FileInfo GetXmlFile(string path)
         {
         {
-            return Path.Combine(path, "collection.xml");
+            return new FileInfo(Path.Combine(path, "collection.xml"));
         }
         }
     }
     }
 }
 }

+ 1 - 1
MediaBrowser.Providers/GameGenres/GameGenreImageProvider.cs

@@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.GameGenres
 
 
         public static string ProviderName
         public static string ProviderName
         {
         {
-            get { return "Media Browser"; }
+            get { return "Media Browser Images"; }
         }
         }
 
 
         public bool Supports(IHasImages item)
         public bool Supports(IHasImages item)

+ 7 - 4
MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs

@@ -1,7 +1,10 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -11,12 +14,12 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.GameGenres
 namespace MediaBrowser.Providers.GameGenres
 {
 {
-    public class GameGenreMetadataService : ConcreteMetadataService<GameGenre, ItemId>
+    public class GameGenreMetadataService : MetadataService<GameGenre, ItemId>
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
-        public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
         }
         }

+ 59 - 0
MediaBrowser.Providers/Games/GameMetadataService.cs

@@ -0,0 +1,59 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Games
+{
+    public class GameMetadataService : MetadataService<Game, GameId>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public GameMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="lockedFields">The locked fields.</param>
+        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+        protected override void MergeData(Game source, Game target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+
+            if (replaceData || string.IsNullOrEmpty(target.GameSystem))
+            {
+                target.GameSystem = source.GameSystem;
+            }
+        }
+
+        protected override Task SaveItem(Game item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+
+        protected override GameId GetId(Game item)
+        {
+            var id = base.GetId(item);
+
+            id.GameSystem = item.GameSystem;
+
+            return id;
+        }
+    }
+}

+ 0 - 93
MediaBrowser.Providers/Games/GameProviderFromXml.cs

@@ -1,93 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Providers.Savers;
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.Games
-{
-    public class GameProviderFromXml : BaseMetadataProvider
-    {
-        private readonly IFileSystem _fileSystem;
-
-        /// <summary>
-        /// 
-        /// </summary>
-        /// <param name="logManager"></param>
-        /// <param name="configurationManager"></param>
-        public GameProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
-        {
-            _fileSystem = fileSystem;
-        }
-
-        /// <summary>
-        /// 
-        /// </summary>
-        /// <param name="item"></param>
-        /// <returns></returns>
-        public override bool Supports(BaseItem item)
-        {
-            return item is Game;
-        }
-
-        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var savePath = GameXmlSaver.GetGameSavePath(item);
-
-            var xml = item.ResolveArgs.GetMetaFileByPath(savePath) ?? new FileInfo(savePath);
-
-            if (!xml.Exists)
-            {
-                return false;
-            }
-
-            return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved;
-        }
-
-        /// <summary>
-        /// 
-        /// </summary>
-        /// <param name="item"></param>
-        /// <param name="force"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            var game = (Game)item;
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var metaFile = GameXmlSaver.GetGameSavePath(game);
-
-            if (File.Exists(metaFile))
-            {
-                await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-                try
-                {
-                    new GameXmlParser(Logger).Fetch(game, metaFile, cancellationToken);
-                }
-                finally
-                {
-                    XmlParsingResourcePool.Release();
-                }
-            }
-
-            SetLastRefreshed(game, DateTime.UtcNow, providerInfo);
-            return true;
-        }
-
-        /// <summary>
-        /// 
-        /// </summary>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.Second; }
-        }
-    }
-}

+ 59 - 0
MediaBrowser.Providers/Games/GameSystemMetadataService.cs

@@ -0,0 +1,59 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Games
+{
+    public class GameSystemMetadataService : MetadataService<GameSystem, GameSystemId>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public GameSystemMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="lockedFields">The locked fields.</param>
+        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+        protected override void MergeData(GameSystem source, GameSystem target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+
+            if (replaceData || string.IsNullOrEmpty(target.GameSystemName))
+            {
+                target.GameSystemName = source.GameSystemName;
+            }
+        }
+
+        protected override Task SaveItem(GameSystem item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+
+        protected override GameSystemId GetId(GameSystem item)
+        {
+            var id = base.GetId(item);
+
+            id.Path = item.Path;
+
+            return id;
+        }
+    }
+}

+ 0 - 92
MediaBrowser.Providers/Games/GameSystemProviderFromXml.cs

@@ -1,92 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.Games
-{
-    public class GameSystemProviderFromXml : BaseMetadataProvider
-    {
-        private readonly IFileSystem _fileSystem;
-
-        public GameSystemProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
-        {
-            _fileSystem = fileSystem;
-        }
-
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            return item is GameSystem && item.LocationType == LocationType.FileSystem;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.Second; }
-        }
-
-        private const string XmlFileName = "gamesystem.xml";
-        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var xml = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
-
-            if (xml == null)
-            {
-                return false;
-            }
-
-            return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved;
-        }
-
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
-
-            if (metadataFile != null)
-            {
-                var path = metadataFile.FullName;
-
-                await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-                try
-                {
-                    new BaseItemXmlParser<GameSystem>(Logger).Fetch((GameSystem)item, path, cancellationToken);
-                }
-                finally
-                {
-                    XmlParsingResourcePool.Release();
-                }
-
-                SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-
-                return true;
-            }
-
-            return false;
-        }
-    }
-}

+ 63 - 0
MediaBrowser.Providers/Games/GameSystemXmlParser.cs

@@ -0,0 +1,63 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml;
+
+namespace MediaBrowser.Providers.Games
+{
+    public class GameSystemXmlParser : BaseItemXmlParser<GameSystem>
+    {
+        public GameSystemXmlParser(ILogger logger)
+            : base(logger)
+        {
+        }
+
+        public Task FetchAsync(GameSystem item, string metadataFile, CancellationToken cancellationToken)
+        {
+            Fetch(item, metadataFile, cancellationToken);
+
+            cancellationToken.ThrowIfCancellationRequested();
+
+            return Task.FromResult(true);
+        }
+
+        /// <summary>
+        /// Fetches the data from XML node.
+        /// </summary>
+        /// <param name="reader">The reader.</param>
+        /// <param name="item">The item.</param>
+        protected override void FetchDataFromXmlNode(XmlReader reader, GameSystem item)
+        {
+            switch (reader.Name)
+            {
+                case "GameSystem":
+                    {
+                        var val = reader.ReadElementContentAsString();
+                        if (!string.IsNullOrWhiteSpace(val))
+                        {
+                            item.GameSystemName = val;
+                        }
+                        break;
+                    }
+
+                case "GamesDbId":
+                    {
+                        var val = reader.ReadElementContentAsString();
+                        if (!string.IsNullOrWhiteSpace(val))
+                        {
+                            item.SetProviderId(MetadataProviders.Gamesdb, val);
+                        }
+                        break;
+                    }
+
+
+                default:
+                    base.FetchDataFromXmlNode(reader, item);
+                    break;
+            }
+        }
+    }
+}

+ 59 - 0
MediaBrowser.Providers/Games/GameSystemXmlProvider.cs

@@ -0,0 +1,59 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Games
+{
+    public class GameSystemXmlProvider : BaseXmlProvider, ILocalMetadataProvider<GameSystem>
+    {
+        private readonly ILogger _logger;
+
+        public GameSystemXmlProvider(IFileSystem fileSystem, ILogger logger)
+            : base(fileSystem)
+        {
+            _logger = logger;
+        }
+
+        public async Task<MetadataResult<GameSystem>> GetMetadata(string path, CancellationToken cancellationToken)
+        {
+            path = GetXmlFile(path).FullName;
+
+            var result = new MetadataResult<GameSystem>();
+
+            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                var item = new GameSystem();
+
+                new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken);
+                result.HasMetadata = true;
+                result.Item = item;
+            }
+            catch (FileNotFoundException)
+            {
+                result.HasMetadata = false;
+            }
+            finally
+            {
+                XmlParsingResourcePool.Release();
+            }
+
+            return result;
+        }
+
+        public string Name
+        {
+            get { return "Media Browser xml"; }
+        }
+
+        protected override FileInfo GetXmlFile(string path)
+        {
+            return new FileInfo(Path.Combine(path, "gamesystem.xml"));
+        }
+    }
+}

+ 2 - 8
MediaBrowser.Providers/Games/GameXmlParser.cs

@@ -14,7 +14,6 @@ namespace MediaBrowser.Providers.Games
     /// </summary>
     /// </summary>
     public class GameXmlParser : BaseItemXmlParser<Game>
     public class GameXmlParser : BaseItemXmlParser<Game>
     {
     {
-        private Task _chaptersTask = null;
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
 
         public GameXmlParser(ILogger logger)
         public GameXmlParser(ILogger logger)
@@ -22,18 +21,13 @@ namespace MediaBrowser.Providers.Games
         {
         {
         }
         }
 
 
-        public async Task FetchAsync(Game item, string metadataFile, CancellationToken cancellationToken)
+        public Task FetchAsync(Game item, string metadataFile, CancellationToken cancellationToken)
         {
         {
-            _chaptersTask = null;
-
             Fetch(item, metadataFile, cancellationToken);
             Fetch(item, metadataFile, cancellationToken);
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            if (_chaptersTask != null)
-            {
-                await _chaptersTask.ConfigureAwait(false);
-            }
+            return Task.FromResult(true);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 74 - 0
MediaBrowser.Providers/Games/GameXmlProvider.cs

@@ -0,0 +1,74 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Games
+{
+    public class GameXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Game>
+    {
+        private readonly ILogger _logger;
+
+        public GameXmlProvider(IFileSystem fileSystem, ILogger logger)
+            : base(fileSystem)
+        {
+            _logger = logger;
+        }
+
+        public async Task<MetadataResult<Game>> GetMetadata(string path, CancellationToken cancellationToken)
+        {
+            path = GetXmlFile(path).FullName;
+
+            var result = new MetadataResult<Game>();
+
+            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                var item = new Game();
+
+                new GameXmlParser(_logger).Fetch(item, path, cancellationToken);
+                result.HasMetadata = true;
+                result.Item = item;
+            }
+            catch (FileNotFoundException)
+            {
+                result.HasMetadata = false;
+            }
+            finally
+            {
+                XmlParsingResourcePool.Release();
+            }
+
+            return result;
+        }
+
+        public string Name
+        {
+            get { return "Media Browser xml"; }
+        }
+
+        protected override FileInfo GetXmlFile(string path)
+        {
+            var fileInfo = FileSystem.GetFileSystemInfo(path);
+
+            var directoryInfo = fileInfo as DirectoryInfo;
+
+            if (directoryInfo == null)
+            {
+                directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path));
+            }
+
+            var directoryPath = directoryInfo.FullName;
+
+            var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(path) + ".xml");
+
+            var file = new FileInfo(specificFile);
+
+            return file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "game.xml"));
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Providers/Genres/GenreImageProvider.cs

@@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.Genres
 
 
         public static string ProviderName
         public static string ProviderName
         {
         {
-            get { return "Media Browser"; }
+            get { return "Media Browser Images"; }
         }
         }
 
 
         public bool Supports(IHasImages item)
         public bool Supports(IHasImages item)

+ 7 - 4
MediaBrowser.Providers/Genres/GenreMetadataService.cs

@@ -1,7 +1,10 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -11,12 +14,12 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.Genres
 namespace MediaBrowser.Providers.Genres
 {
 {
-    public class GenreMetadataService : ConcreteMetadataService<Genre, ItemId>
+    public class GenreMetadataService : MetadataService<Genre, ItemId>
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
-        public GenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public GenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
         }
         }

+ 7 - 4
MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs

@@ -1,7 +1,10 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -11,12 +14,12 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.LiveTv
 namespace MediaBrowser.Providers.LiveTv
 {
 {
-    public class ChannelMetadataService : ConcreteMetadataService<LiveTvChannel, ItemId>
+    public class ChannelMetadataService : MetadataService<LiveTvChannel, ItemId>
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
-        public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
         }
         }

+ 4 - 4
MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.LiveTv
 
 
         public async Task<MetadataResult<LiveTvChannel>> GetMetadata(string path, CancellationToken cancellationToken)
         public async Task<MetadataResult<LiveTvChannel>> GetMetadata(string path, CancellationToken cancellationToken)
         {
         {
-            path = GetXmlPath(path);
+            path = GetXmlFile(path).FullName;
 
 
             var result = new MetadataResult<LiveTvChannel>();
             var result = new MetadataResult<LiveTvChannel>();
 
 
@@ -48,12 +48,12 @@ namespace MediaBrowser.Providers.LiveTv
 
 
         public string Name
         public string Name
         {
         {
-            get { return "Media Browser Xml"; }
+            get { return "Media Browser xml"; }
         }
         }
 
 
-        protected override string GetXmlPath(string path)
+        protected override FileInfo GetXmlFile(string path)
         {
         {
-            return Path.Combine(path, "channel.xml");
+            return new FileInfo(Path.Combine(path, "channel.xml"));
         }
         }
     }
     }
 }
 }

+ 7 - 4
MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs

@@ -1,7 +1,10 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -11,12 +14,12 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.LiveTv
 namespace MediaBrowser.Providers.LiveTv
 {
 {
-    public class ProgramMetadataService : ConcreteMetadataService<LiveTvProgram, ItemId>
+    public class ProgramMetadataService : MetadataService<LiveTvProgram, ItemId>
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
-        public ProgramMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public ProgramMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
         }
         }

+ 0 - 21
MediaBrowser.Providers/Manager/ConcreteMetadataService.cs

@@ -1,21 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Logging;
-
-namespace MediaBrowser.Providers.Manager
-{
-    public abstract class ConcreteMetadataService<TItemType, TIdType> : MetadataService<TItemType, TIdType>
-        where TItemType : IHasMetadata, new()
-        where TIdType : ItemId, new()
-    {
-        protected ConcreteMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
-        {
-        }
-
-        protected override TItemType CreateNew()
-        {
-            return new TItemType();
-        }
-    }
-}

+ 0 - 9
MediaBrowser.Providers/Manager/ImageSaver.cs

@@ -309,15 +309,6 @@ namespace MediaBrowser.Providers.Manager
                     {
                     {
                         item.BackdropImagePaths.Add(path);
                         item.BackdropImagePaths.Add(path);
                     }
                     }
-
-                    if (string.IsNullOrEmpty(sourceUrl))
-                    {
-                        item.RemoveImageSourceForPath(path);
-                    }
-                    else
-                    {
-                        item.AddImageSource(path, sourceUrl);
-                    }
                     break;
                     break;
                 default:
                 default:
                     item.SetImagePath(type, path);
                     item.SetImagePath(type, path);

+ 38 - 53
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -1,16 +1,17 @@
-using System.IO;
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Net;
 using System.Net;
 using System.Threading;
 using System.Threading;
@@ -25,11 +26,12 @@ namespace MediaBrowser.Providers.Manager
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
 
 
-        public ItemImageProvider(ILogger logger, IProviderManager providerManager, IServerConfigurationManager config)
+        public ItemImageProvider(ILogger logger, IProviderManager providerManager, IServerConfigurationManager config, IFileSystem fileSystem)
         {
         {
             _logger = logger;
             _logger = logger;
             _providerManager = providerManager;
             _providerManager = providerManager;
             _config = config;
             _config = config;
+            _fileSystem = fileSystem;
         }
         }
 
 
         public bool ValidateImages(IHasImages item, IEnumerable<IImageProvider> providers)
         public bool ValidateImages(IHasImages item, IEnumerable<IImageProvider> providers)
@@ -49,7 +51,7 @@ namespace MediaBrowser.Providers.Manager
             return hasChanges;
             return hasChanges;
         }
         }
 
 
-        public async Task<RefreshResult> RefreshImages(IHasImages item, IEnumerable<IImageProvider> imageProviders, ImageRefreshOptions options, CancellationToken cancellationToken)
+        public async Task<RefreshResult> RefreshImages(IHasImages item, IEnumerable<IImageProvider> imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken)
         {
         {
             var result = new RefreshResult { UpdateType = ItemUpdateType.Unspecified };
             var result = new RefreshResult { UpdateType = ItemUpdateType.Unspecified };
 
 
@@ -57,16 +59,20 @@ namespace MediaBrowser.Providers.Manager
 
 
             var providerIds = new List<Guid>();
             var providerIds = new List<Guid>();
 
 
+            // In order to avoid duplicates, only download these if there are none already
+            var backdropLimit = item.HasImage(ImageType.Backdrop) ? 0 : savedOptions.GetLimit(ImageType.Backdrop);
+            var screenshotLimit = item.HasImage(ImageType.Screenshot) ? 0 : savedOptions.GetLimit(ImageType.Screenshot);
+
             foreach (var provider in providers.OfType<IRemoteImageProvider>())
             foreach (var provider in providers.OfType<IRemoteImageProvider>())
             {
             {
-                await RefreshFromProvider(item, provider, options, result, cancellationToken).ConfigureAwait(false);
+                await RefreshFromProvider(item, provider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, result, cancellationToken).ConfigureAwait(false);
 
 
                 providerIds.Add(provider.GetType().FullName.GetMD5());
                 providerIds.Add(provider.GetType().FullName.GetMD5());
             }
             }
 
 
             foreach (var provider in providers.OfType<IDynamicImageProvider>())
             foreach (var provider in providers.OfType<IDynamicImageProvider>())
             {
             {
-                await RefreshFromProvider(item, provider, result, cancellationToken).ConfigureAwait(false);
+                await RefreshFromProvider(item, provider, savedOptions, result, cancellationToken).ConfigureAwait(false);
 
 
                 providerIds.Add(provider.GetType().FullName.GetMD5());
                 providerIds.Add(provider.GetType().FullName.GetMD5());
             }
             }
@@ -81,10 +87,11 @@ namespace MediaBrowser.Providers.Manager
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="provider">The provider.</param>
         /// <param name="provider">The provider.</param>
+        /// <param name="savedOptions">The saved options.</param>
         /// <param name="result">The result.</param>
         /// <param name="result">The result.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        private async Task RefreshFromProvider(IHasImages item, IDynamicImageProvider provider, RefreshResult result, CancellationToken cancellationToken)
+        private async Task RefreshFromProvider(IHasImages item, IDynamicImageProvider provider, MetadataOptions savedOptions, RefreshResult result, CancellationToken cancellationToken)
         {
         {
             _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
             _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
 
 
@@ -94,7 +101,7 @@ namespace MediaBrowser.Providers.Manager
 
 
                 foreach (var imageType in images)
                 foreach (var imageType in images)
                 {
                 {
-                    if (!item.HasImage(imageType))
+                    if (!item.HasImage(imageType) && savedOptions.IsEnabled(imageType))
                     {
                     {
                         var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false);
                         var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false);
 
 
@@ -152,17 +159,20 @@ namespace MediaBrowser.Providers.Manager
         /// <summary>
         /// <summary>
         /// Determines if an item already contains the given images
         /// Determines if an item already contains the given images
         /// </summary>
         /// </summary>
-        /// <param name="item"></param>
-        /// <param name="images"></param>
-        /// <returns></returns>
-        private bool ContainsImages(IHasImages item, List<ImageType> images)
+        /// <param name="item">The item.</param>
+        /// <param name="images">The images.</param>
+        /// <param name="savedOptions">The saved options.</param>
+        /// <param name="backdropLimit">The backdrop limit.</param>
+        /// <param name="screenshotLimit">The screenshot limit.</param>
+        /// <returns><c>true</c> if the specified item contains images; otherwise, <c>false</c>.</returns>
+        private bool ContainsImages(IHasImages item, List<ImageType> images, MetadataOptions savedOptions, int backdropLimit, int screenshotLimit)
         {
         {
-            if (_singularImages.Any(i => images.Contains(i) && !item.HasImage(i)))
+            if (_singularImages.Any(i => images.Contains(i) && !item.HasImage(i) && savedOptions.GetLimit(i) > 0))
             {
             {
                 return false;
                 return false;
             }
             }
 
 
-            if (images.Contains(ImageType.Backdrop) && item.BackdropImagePaths.Count < GetMaxBackdropCount(item))
+            if (images.Contains(ImageType.Backdrop) && item.BackdropImagePaths.Count < backdropLimit)
             {
             {
                 return false;
                 return false;
             }
             }
@@ -172,7 +182,7 @@ namespace MediaBrowser.Providers.Manager
                 var hasScreenshots = item as IHasScreenshots;
                 var hasScreenshots = item as IHasScreenshots;
                 if (hasScreenshots != null)
                 if (hasScreenshots != null)
                 {
                 {
-                    if (hasScreenshots.ScreenshotImagePaths.Count < GetMaxBackdropCount(item))
+                    if (hasScreenshots.ScreenshotImagePaths.Count < screenshotLimit)
                     {
                     {
                         return false;
                         return false;
                     }
                     }
@@ -187,16 +197,18 @@ namespace MediaBrowser.Providers.Manager
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="provider">The provider.</param>
         /// <param name="provider">The provider.</param>
-        /// <param name="options">The options.</param>
+        /// <param name="refreshOptions">The refresh options.</param>
+        /// <param name="savedOptions">The saved options.</param>
+        /// <param name="backdropLimit">The backdrop limit.</param>
+        /// <param name="screenshotLimit">The screenshot limit.</param>
         /// <param name="result">The result.</param>
         /// <param name="result">The result.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        private async Task RefreshFromProvider(IHasImages item, IRemoteImageProvider provider, ImageRefreshOptions options, RefreshResult result, CancellationToken cancellationToken)
+        private async Task RefreshFromProvider(IHasImages item, IRemoteImageProvider provider, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, int backdropLimit, int screenshotLimit, RefreshResult result, CancellationToken cancellationToken)
         {
         {
             try
             try
             {
             {
-                // TODO: Also factor in IsConfiguredToDownloadImage
-                if (ContainsImages(item, provider.GetSupportedImages(item).ToList()))
+                if (ContainsImages(item, provider.GetSupportedImages(item).ToList(), savedOptions, backdropLimit, screenshotLimit))
                 {
                 {
                     return;
                     return;
                 }
                 }
@@ -208,18 +220,18 @@ namespace MediaBrowser.Providers.Manager
 
 
                 foreach (var type in _singularImages)
                 foreach (var type in _singularImages)
                 {
                 {
-                    if (IsConfiguredToDownloadImage(item, type) && !item.HasImage(type))
+                    if (savedOptions.IsEnabled(type) && !item.HasImage(type))
                     {
                     {
                         await DownloadImage(item, provider, result, list, type, cancellationToken).ConfigureAwait(false);
                         await DownloadImage(item, provider, result, list, type, cancellationToken).ConfigureAwait(false);
                     }
                     }
                 }
                 }
 
 
-                await DownloadBackdrops(item, provider, result, list, cancellationToken).ConfigureAwait(false);
+                await DownloadBackdrops(item, backdropLimit, provider, result, list, cancellationToken).ConfigureAwait(false);
 
 
                 var hasScreenshots = item as IHasScreenshots;
                 var hasScreenshots = item as IHasScreenshots;
                 if (hasScreenshots != null)
                 if (hasScreenshots != null)
                 {
                 {
-                    await DownloadScreenshots(hasScreenshots, provider, result, list, cancellationToken).ConfigureAwait(false);
+                    await DownloadScreenshots(hasScreenshots, screenshotLimit, provider, result, list, cancellationToken).ConfigureAwait(false);
                 }
                 }
             }
             }
             catch (OperationCanceledException)
             catch (OperationCanceledException)
@@ -342,25 +354,19 @@ namespace MediaBrowser.Providers.Manager
             }
             }
         }
         }
 
 
-        private async Task DownloadBackdrops(IHasImages item, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, CancellationToken cancellationToken)
+        private async Task DownloadBackdrops(IHasImages item, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, CancellationToken cancellationToken)
         {
         {
             const ImageType imageType = ImageType.Backdrop;
             const ImageType imageType = ImageType.Backdrop;
-            var maxCount = GetMaxBackdropCount(item);
 
 
             foreach (var image in images.Where(i => i.Type == imageType))
             foreach (var image in images.Where(i => i.Type == imageType))
             {
             {
-                if (item.BackdropImagePaths.Count >= maxCount)
+                if (item.BackdropImagePaths.Count >= limit)
                 {
                 {
                     break;
                     break;
                 }
                 }
 
 
                 var url = image.Url;
                 var url = image.Url;
 
 
-                if (item.ContainsImageWithSourceUrl(url))
-                {
-                    continue;
-                }
-
                 try
                 try
                 {
                 {
                     var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
                     var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
@@ -381,25 +387,19 @@ namespace MediaBrowser.Providers.Manager
             }
             }
         }
         }
 
 
-        private async Task DownloadScreenshots(IHasScreenshots item, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, CancellationToken cancellationToken)
+        private async Task DownloadScreenshots(IHasScreenshots item, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, CancellationToken cancellationToken)
         {
         {
             const ImageType imageType = ImageType.Screenshot;
             const ImageType imageType = ImageType.Screenshot;
-            var maxCount = GetMaxScreenshotCount(item);
 
 
             foreach (var image in images.Where(i => i.Type == imageType))
             foreach (var image in images.Where(i => i.Type == imageType))
             {
             {
-                if (item.ScreenshotImagePaths.Count >= maxCount)
+                if (item.ScreenshotImagePaths.Count >= limit)
                 {
                 {
                     break;
                     break;
                 }
                 }
 
 
                 var url = image.Url;
                 var url = image.Url;
 
 
-                if (item.ContainsImageWithSourceUrl(url))
-                {
-                    continue;
-                }
-
                 try
                 try
                 {
                 {
                     var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
                     var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
@@ -419,20 +419,5 @@ namespace MediaBrowser.Providers.Manager
                 }
                 }
             }
             }
         }
         }
-
-        private bool IsConfiguredToDownloadImage(IHasImages item, ImageType type)
-        {
-            return true;
-        }
-
-        private int GetMaxBackdropCount(IHasImages item)
-        {
-            return 1;
-        }
-
-        private int GetMaxScreenshotCount(IHasScreenshots item)
-        {
-            return 1;
-        }
     }
     }
 }
 }

+ 56 - 28
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using System;
 using System;
@@ -13,32 +15,22 @@ using System.Threading.Tasks;
 namespace MediaBrowser.Providers.Manager
 namespace MediaBrowser.Providers.Manager
 {
 {
     public abstract class MetadataService<TItemType, TIdType> : IMetadataService
     public abstract class MetadataService<TItemType, TIdType> : IMetadataService
-        where TItemType : IHasMetadata
+        where TItemType : IHasMetadata, new()
         where TIdType : ItemId, new()
         where TIdType : ItemId, new()
     {
     {
         protected readonly IServerConfigurationManager ServerConfigurationManager;
         protected readonly IServerConfigurationManager ServerConfigurationManager;
         protected readonly ILogger Logger;
         protected readonly ILogger Logger;
         protected readonly IProviderManager ProviderManager;
         protected readonly IProviderManager ProviderManager;
-        private readonly IProviderRepository _providerRepo;
+        protected readonly IProviderRepository ProviderRepo;
+        protected readonly IFileSystem FileSystem;
 
 
-        private IMetadataProvider<TItemType>[] _providers = { };
-
-        protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo)
+        protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem)
         {
         {
             ServerConfigurationManager = serverConfigurationManager;
             ServerConfigurationManager = serverConfigurationManager;
             Logger = logger;
             Logger = logger;
             ProviderManager = providerManager;
             ProviderManager = providerManager;
-            _providerRepo = providerRepo;
-        }
-
-        /// <summary>
-        /// Adds the parts.
-        /// </summary>
-        /// <param name="providers">The providers.</param>
-        public void AddParts(IEnumerable<IMetadataProvider> providers)
-        {
-            _providers = providers.OfType<IMetadataProvider<TItemType>>()
-                .ToArray();
+            ProviderRepo = providerRepo;
+            FileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -48,7 +40,7 @@ namespace MediaBrowser.Providers.Manager
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         protected Task SaveProviderResult(MetadataStatus result)
         protected Task SaveProviderResult(MetadataStatus result)
         {
         {
-            return _providerRepo.SaveMetadataStatus(result, CancellationToken.None);
+            return ProviderRepo.SaveMetadataStatus(result, CancellationToken.None);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -58,20 +50,22 @@ namespace MediaBrowser.Providers.Manager
         /// <returns>ProviderResult.</returns>
         /// <returns>ProviderResult.</returns>
         protected MetadataStatus GetLastResult(Guid itemId)
         protected MetadataStatus GetLastResult(Guid itemId)
         {
         {
-            return _providerRepo.GetMetadataStatus(itemId) ?? new MetadataStatus { ItemId = itemId };
+            return ProviderRepo.GetMetadataStatus(itemId) ?? new MetadataStatus { ItemId = itemId };
         }
         }
 
 
-        public async Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken)
+        public async Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
         {
         {
             var itemOfType = (TItemType)item;
             var itemOfType = (TItemType)item;
 
 
+            var config = GetMetadataOptions(itemOfType);
+
             var updateType = ItemUpdateType.Unspecified;
             var updateType = ItemUpdateType.Unspecified;
             var lastResult = GetLastResult(item.Id);
             var lastResult = GetLastResult(item.Id);
             var refreshResult = lastResult;
             var refreshResult = lastResult;
             refreshResult.LastErrorMessage = string.Empty;
             refreshResult.LastErrorMessage = string.Empty;
             refreshResult.LastStatus = ProviderRefreshStatus.Success;
             refreshResult.LastStatus = ProviderRefreshStatus.Success;
 
 
-            var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager);
+            var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem);
             var localImagesFailed = false;
             var localImagesFailed = false;
 
 
             var allImageProviders = ((ProviderManager)ProviderManager).GetImageProviders(item).ToList();
             var allImageProviders = ((ProviderManager)ProviderManager).GetImageProviders(item).ToList();
@@ -93,13 +87,13 @@ namespace MediaBrowser.Providers.Manager
             }
             }
 
 
             // Next run metadata providers
             // Next run metadata providers
-            if (options.MetadataRefreshMode != MetadataRefreshMode.None)
+            if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
             {
             {
-                var providers = GetProviders(item, lastResult.DateLastMetadataRefresh.HasValue, options).ToList();
+                var providers = GetProviders(item, lastResult.DateLastMetadataRefresh.HasValue, refreshOptions).ToList();
 
 
                 if (providers.Count > 0)
                 if (providers.Count > 0)
                 {
                 {
-                    var result = await RefreshWithProviders(itemOfType, options, providers, cancellationToken).ConfigureAwait(false);
+                    var result = await RefreshWithProviders(itemOfType, refreshOptions, providers, cancellationToken).ConfigureAwait(false);
 
 
                     updateType = updateType | result.UpdateType;
                     updateType = updateType | result.UpdateType;
                     refreshResult.AddStatus(result.Status, result.ErrorMessage);
                     refreshResult.AddStatus(result.Status, result.ErrorMessage);
@@ -109,13 +103,13 @@ namespace MediaBrowser.Providers.Manager
             }
             }
 
 
             // Next run remote image providers, but only if local image providers didn't throw an exception
             // Next run remote image providers, but only if local image providers didn't throw an exception
-            if (!localImagesFailed && options.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
+            if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
             {
             {
-                var providers = GetNonLocalImageProviders(item, allImageProviders, lastResult.DateLastImagesRefresh.HasValue, options).ToList();
+                var providers = GetNonLocalImageProviders(item, allImageProviders, lastResult.DateLastImagesRefresh.HasValue, refreshOptions).ToList();
 
 
                 if (providers.Count > 0)
                 if (providers.Count > 0)
                 {
                 {
-                    var result = await itemImageProvider.RefreshImages(itemOfType, providers, options, cancellationToken).ConfigureAwait(false);
+                    var result = await itemImageProvider.RefreshImages(itemOfType, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false);
 
 
                     updateType = updateType | result.UpdateType;
                     updateType = updateType | result.UpdateType;
                     refreshResult.AddStatus(result.Status, result.ErrorMessage);
                     refreshResult.AddStatus(result.Status, result.ErrorMessage);
@@ -128,7 +122,7 @@ namespace MediaBrowser.Providers.Manager
 
 
             var providersHadChanges = updateType > ItemUpdateType.Unspecified;
             var providersHadChanges = updateType > ItemUpdateType.Unspecified;
 
 
-            if (options.ForceSave || providersHadChanges)
+            if (refreshOptions.ForceSave || providersHadChanges)
             {
             {
                 if (string.IsNullOrEmpty(item.Name))
                 if (string.IsNullOrEmpty(item.Name))
                 {
                 {
@@ -145,6 +139,15 @@ namespace MediaBrowser.Providers.Manager
             }
             }
         }
         }
 
 
+        private readonly MetadataOptions _defaultOptions = new MetadataOptions();
+        protected MetadataOptions GetMetadataOptions(TItemType item)
+        {
+            var type = item.GetType().Name;
+            return ServerConfigurationManager.Configuration.MetadataOptions
+                .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ?? 
+                _defaultOptions;
+        }
+
         /// <summary>
         /// <summary>
         /// Afters the metadata refresh.
         /// Afters the metadata refresh.
         /// </summary>
         /// </summary>
@@ -292,10 +295,35 @@ namespace MediaBrowser.Providers.Manager
                 MergeData(temp, item, item.LockedFields, true, true);
                 MergeData(temp, item, item.LockedFields, true, true);
             }
             }
 
 
+            foreach (var provider in providers.OfType<ICustomMetadataProvider<TItemType>>())
+            {
+                Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
+
+                try
+                {
+                    await provider.FetchAsync(item, cancellationToken).ConfigureAwait(false);
+
+                    refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload;
+                }
+                catch (OperationCanceledException)
+                {
+                    throw;
+                }
+                catch (Exception ex)
+                {
+                    refreshResult.Status = ProviderRefreshStatus.CompletedWithErrors;
+                    refreshResult.ErrorMessage = ex.Message;
+                    Logger.ErrorException("Error in {0}", ex, provider.Name);
+                }
+            }
+            
             return refreshResult;
             return refreshResult;
         }
         }
 
 
-        protected abstract TItemType CreateNew();
+        protected virtual TItemType CreateNew()
+        {
+            return new TItemType();
+        }
 
 
         private async Task ExecuteRemoteProviders(TItemType item, TItemType temp, IEnumerable<IRemoteMetadataProvider<TItemType>> providers, RefreshResult refreshResult, CancellationToken cancellationToken)
         private async Task ExecuteRemoteProviders(TItemType item, TItemType temp, IEnumerable<IRemoteMetadataProvider<TItemType>> providers, RefreshResult refreshResult, CancellationToken cancellationToken)
         {
         {

+ 162 - 6
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -2,14 +2,17 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
 using System;
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
@@ -58,6 +61,7 @@ namespace MediaBrowser.Providers.Manager
 
 
         private IMetadataService[] _metadataServices = { };
         private IMetadataService[] _metadataServices = { };
         private IMetadataProvider[] _metadataProviders = { };
         private IMetadataProvider[] _metadataProviders = { };
+        private IEnumerable<IMetadataSaver> _savers;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ProviderManager" /> class.
         /// Initializes a new instance of the <see cref="ProviderManager" /> class.
@@ -85,7 +89,8 @@ namespace MediaBrowser.Providers.Manager
         /// <param name="imageProviders">The image providers.</param>
         /// <param name="imageProviders">The image providers.</param>
         /// <param name="metadataServices">The metadata services.</param>
         /// <param name="metadataServices">The metadata services.</param>
         /// <param name="metadataProviders">The metadata providers.</param>
         /// <param name="metadataProviders">The metadata providers.</param>
-        public void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders)
+        /// <param name="metadataSavers">The metadata savers.</param>
+        public void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers)
         {
         {
             MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
             MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
 
 
@@ -93,6 +98,7 @@ namespace MediaBrowser.Providers.Manager
 
 
             _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
             _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
             _metadataProviders = metadataProviders.ToArray();
             _metadataProviders = metadataProviders.ToArray();
+            _savers = metadataSavers.ToArray();
         }
         }
 
 
         public Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken)
         public Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken)
@@ -479,9 +485,15 @@ namespace MediaBrowser.Providers.Manager
 
 
         public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(IHasMetadata item)
         public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(IHasMetadata item)
             where T : IHasMetadata
             where T : IHasMetadata
+        {
+            return GetMetadataProvidersInternal<T>(item, false);
+        }
+
+        private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(IHasMetadata item, bool includeDisabled)
+            where T : IHasMetadata
         {
         {
             return _metadataProviders.OfType<IMetadataProvider<T>>()
             return _metadataProviders.OfType<IMetadataProvider<T>>()
-                .Where(i => CanRefresh(i, item))
+                .Where(i => CanRefresh(i, item, includeDisabled))
                 .OrderBy(i => GetOrder(item, i));
                 .OrderBy(i => GetOrder(item, i));
         }
         }
 
 
@@ -495,10 +507,11 @@ namespace MediaBrowser.Providers.Manager
         /// </summary>
         /// </summary>
         /// <param name="provider">The provider.</param>
         /// <param name="provider">The provider.</param>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
+        /// <param name="includeDisabled">if set to <c>true</c> [include disabled].</param>
         /// <returns><c>true</c> if this instance can refresh the specified provider; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if this instance can refresh the specified provider; otherwise, <c>false</c>.</returns>
-        protected bool CanRefresh(IMetadataProvider provider, IHasMetadata item)
+        private bool CanRefresh(IMetadataProvider provider, IHasMetadata item, bool includeDisabled)
         {
         {
-            if (!ConfigurationManager.Configuration.EnableInternetProviders && provider is IRemoteMetadataProvider)
+            if (!includeDisabled && !ConfigurationManager.Configuration.EnableInternetProviders && provider is IRemoteMetadataProvider)
             {
             {
                 return false;
                 return false;
             }
             }
@@ -546,5 +559,148 @@ namespace MediaBrowser.Providers.Manager
 
 
             return hasOrder.Order;
             return hasOrder.Order;
         }
         }
+
+        public IEnumerable<MetadataPluginSummary> GetAllMetadataPlugins()
+        {
+            var list = new List<MetadataPluginSummary>();
+
+            list.Add(GetPluginSummary<Game>());
+            list.Add(GetPluginSummary<GameSystem>());
+            list.Add(GetPluginSummary<Movie>());
+            list.Add(GetPluginSummary<Trailer>());
+            list.Add(GetPluginSummary<BoxSet>());
+            list.Add(GetPluginSummary<Book>());
+            list.Add(GetPluginSummary<Series>());
+            list.Add(GetPluginSummary<Season>());
+            list.Add(GetPluginSummary<Episode>());
+            list.Add(GetPluginSummary<Person>());
+            list.Add(GetPluginSummary<MusicAlbum>());
+            list.Add(GetPluginSummary<MusicArtist>());
+            list.Add(GetPluginSummary<Audio>());
+
+            list.Add(GetPluginSummary<Genre>());
+            list.Add(GetPluginSummary<Studio>());
+            list.Add(GetPluginSummary<GameGenre>());
+            list.Add(GetPluginSummary<MusicGenre>());
+            
+            return list;
+        }
+
+        private MetadataPluginSummary GetPluginSummary<T>()
+            where T : BaseItem, new()
+        {
+            // Give it a dummy path just so that it looks like a file system item
+            var dummy = new T()
+            {
+                Path = "C:\\",
+
+                // Dummy this up to fool the local trailer check
+                Parent = new Folder()
+            };
+
+            var summary = new MetadataPluginSummary
+            {
+                ItemType = typeof(T).Name
+            };
+
+            var imageProviders = GetImageProviders(dummy).ToList();
+
+            AddMetadataPlugins(summary.Plugins, dummy);
+            AddImagePlugins(summary.Plugins, dummy, imageProviders);
+
+            summary.SupportedImageTypes = imageProviders.OfType<IRemoteImageProvider>()
+                .SelectMany(i => i.GetSupportedImages(dummy))
+                .Distinct()
+                .ToList();
+
+            return summary;
+        }
+
+        private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item)
+            where T : IHasMetadata
+        {
+            var providers = GetMetadataProvidersInternal<T>(item, true).ToList();
+
+            // Locals
+            list.AddRange(providers.Where(i => (i is ILocalMetadataProvider)).Select(i => new MetadataPlugin
+            {
+                Name = i.Name,
+                Type = MetadataPluginType.LocalMetadataProvider
+            }));
+
+            // Fetchers
+            list.AddRange(providers.Where(i => !(i is ILocalMetadataProvider)).Select(i => new MetadataPlugin
+            {
+                Name = i.Name,
+                Type = MetadataPluginType.MetadataFetcher
+            }));
+
+            // Savers
+            list.AddRange(_savers.Where(i => i.IsEnabledFor(item, ItemUpdateType.MetadataEdit)).OrderBy(i => i.Name).Select(i => new MetadataPlugin
+            {
+                Name = i.Name,
+                Type = MetadataPluginType.MetadataSaver
+            }));
+        }
+
+        private void AddImagePlugins<T>(List<MetadataPlugin> list, T item, List<IImageProvider> imageProviders)
+            where T : IHasImages
+        {
+
+            // Locals
+            list.AddRange(imageProviders.Where(i => (i is ILocalImageProvider)).Select(i => new MetadataPlugin
+            {
+                Name = i.Name,
+                Type = MetadataPluginType.LocalImageProvider
+            }));
+
+            // Fetchers
+            list.AddRange(imageProviders.Where(i => !(i is ILocalImageProvider)).Select(i => new MetadataPlugin
+            {
+                Name = i.Name,
+                Type = MetadataPluginType.ImageFetcher
+            }));
+        }
+
+        private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
+
+        /// <summary>
+        /// Saves the metadata.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="updateType">Type of the update.</param>
+        /// <returns>Task.</returns>
+        public async Task SaveMetadata(IHasMetadata item, ItemUpdateType updateType)
+        {
+            var locationType = item.LocationType;
+            if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
+            {
+                throw new ArgumentException("Only file-system based items can save metadata.");
+            }
+
+            foreach (var saver in _savers.Where(i => i.IsEnabledFor(item, updateType)))
+            {
+                var path = saver.GetSavePath(item);
+
+                var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1));
+
+                await semaphore.WaitAsync().ConfigureAwait(false);
+
+                try
+                {
+                    _libraryMonitor.ReportFileSystemChangeBeginning(path);
+                    saver.Save(item, CancellationToken.None);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error in metadata saver", ex);
+                }
+                finally
+                {
+                    _libraryMonitor.ReportFileSystemChangeComplete(path, false);
+                    semaphore.Release();
+                }
+            }
+        }
     }
     }
 }
 }

+ 5 - 3
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -69,11 +69,13 @@
     <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
     <Compile Include="GameGenres\GameGenreMetadataService.cs" />
     <Compile Include="GameGenres\GameGenreMetadataService.cs" />
+    <Compile Include="Games\GameMetadataService.cs" />
+    <Compile Include="Games\GameSystemMetadataService.cs" />
+    <Compile Include="Games\GameSystemXmlParser.cs" />
     <Compile Include="Genres\GenreMetadataService.cs" />
     <Compile Include="Genres\GenreMetadataService.cs" />
     <Compile Include="LiveTv\ChannelMetadataService.cs" />
     <Compile Include="LiveTv\ChannelMetadataService.cs" />
     <Compile Include="LiveTv\ChannelXmlProvider.cs" />
     <Compile Include="LiveTv\ChannelXmlProvider.cs" />
     <Compile Include="LiveTv\ProgramMetadataService.cs" />
     <Compile Include="LiveTv\ProgramMetadataService.cs" />
-    <Compile Include="Manager\ConcreteMetadataService.cs" />
     <Compile Include="Manager\ImageSaver.cs" />
     <Compile Include="Manager\ImageSaver.cs" />
     <Compile Include="Manager\ItemImageProvider.cs" />
     <Compile Include="Manager\ItemImageProvider.cs" />
     <Compile Include="Manager\ProviderManager.cs" />
     <Compile Include="Manager\ProviderManager.cs" />
@@ -82,8 +84,8 @@
     <Compile Include="CollectionFolderImageProvider.cs" />
     <Compile Include="CollectionFolderImageProvider.cs" />
     <Compile Include="FolderProviderFromXml.cs" />
     <Compile Include="FolderProviderFromXml.cs" />
     <Compile Include="Games\GameXmlParser.cs" />
     <Compile Include="Games\GameXmlParser.cs" />
-    <Compile Include="Games\GameProviderFromXml.cs" />
-    <Compile Include="Games\GameSystemProviderFromXml.cs" />
+    <Compile Include="Games\GameXmlProvider.cs" />
+    <Compile Include="Games\GameSystemXmlProvider.cs" />
     <Compile Include="ImageFromMediaLocationProvider.cs" />
     <Compile Include="ImageFromMediaLocationProvider.cs" />
     <Compile Include="ImagesByNameProvider.cs" />
     <Compile Include="ImagesByNameProvider.cs" />
     <Compile Include="Movies\MovieDbSearch.cs" />
     <Compile Include="Movies\MovieDbSearch.cs" />

+ 12 - 8
MediaBrowser.Providers/Movies/FanArtMovieProvider.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
@@ -271,50 +272,53 @@ namespace MediaBrowser.Providers.Movies
         {
         {
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            if (ConfigurationManager.Configuration.DownloadMovieImages.Primary && !item.HasImage(ImageType.Primary))
+            var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
+
+            if (options.IsEnabled(ImageType.Primary) && !item.HasImage(ImageType.Primary))
             {
             {
                 await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
                 await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
             }
             }
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            if (ConfigurationManager.Configuration.DownloadMovieImages.Logo && !item.HasImage(ImageType.Logo))
+            if (options.IsEnabled(ImageType.Logo) && !item.HasImage(ImageType.Logo))
             {
             {
                 await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
                 await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
             }
             }
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            if (ConfigurationManager.Configuration.DownloadMovieImages.Art && !item.HasImage(ImageType.Art))
+            if (options.IsEnabled(ImageType.Art) && !item.HasImage(ImageType.Art))
             {
             {
                 await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
                 await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
             }
             }
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            if (ConfigurationManager.Configuration.DownloadMovieImages.Disc && !item.HasImage(ImageType.Disc))
+            if (options.IsEnabled(ImageType.Disc) && !item.HasImage(ImageType.Disc))
             {
             {
                 await SaveImage(item, images, ImageType.Disc, cancellationToken).ConfigureAwait(false);
                 await SaveImage(item, images, ImageType.Disc, cancellationToken).ConfigureAwait(false);
             }
             }
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            if (ConfigurationManager.Configuration.DownloadMovieImages.Banner && !item.HasImage(ImageType.Banner))
+            if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
             {
             {
                 await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
                 await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
             }
             }
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            if (ConfigurationManager.Configuration.DownloadMovieImages.Thumb && !item.HasImage(ImageType.Thumb))
+            if (options.IsEnabled(ImageType.Thumb) && !item.HasImage(ImageType.Thumb))
             {
             {
                 await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
                 await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
             }
             }
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            var backdropLimit = ConfigurationManager.Configuration.MovieOptions.MaxBackdrops;
-            if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops &&
+            var backdropLimit = options.GetLimit(ImageType.Backdrop);
+
+            if (backdropLimit > 0 &&
                 item.BackdropImagePaths.Count < backdropLimit)
                 item.BackdropImagePaths.Count < backdropLimit)
             {
             {
                 foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
                 foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))

+ 15 - 13
MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
@@ -126,9 +127,11 @@ namespace MediaBrowser.Providers.Movies
                 return false;
                 return false;
             }
             }
 
 
+            var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
+            
             // Don't refresh if we already have both poster and backdrop and we're not refreshing images
             // Don't refresh if we already have both poster and backdrop and we're not refreshing images
             if (item.HasImage(ImageType.Primary) &&
             if (item.HasImage(ImageType.Primary) &&
-                item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MovieOptions.MaxBackdrops &&
+                item.BackdropImagePaths.Count >= options.GetLimit(ImageType.Backdrop) &&
                 !item.LockedFields.Contains(MetadataFields.Images))
                 !item.LockedFields.Contains(MetadataFields.Images))
             {
             {
                 return false;
                 return false;
@@ -205,15 +208,17 @@ namespace MediaBrowser.Providers.Movies
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
+            var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
+            
             var eligibleBackdrops = images
             var eligibleBackdrops = images
-                .Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= ConfigurationManager.Configuration.MovieOptions.MinBackdropWidth)
+                .Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= options.GetMinWidth(ImageType.Backdrop))
                 .ToList();
                 .ToList();
 
 
-            var backdropLimit = ConfigurationManager.Configuration.MovieOptions.MaxBackdrops;
+            var backdropLimit = options.GetLimit(ImageType.Backdrop);
 
 
             // backdrops - only download if earlier providers didn't find any (fanart)
             // backdrops - only download if earlier providers didn't find any (fanart)
             if (eligibleBackdrops.Count > 0 &&
             if (eligibleBackdrops.Count > 0 &&
-                ConfigurationManager.Configuration.DownloadMovieImages.Backdrops &&
+                options.IsEnabled(ImageType.Backdrop) &&
                 item.BackdropImagePaths.Count < backdropLimit &&
                 item.BackdropImagePaths.Count < backdropLimit &&
                 !item.LockedFields.Contains(MetadataFields.Backdrops))
                 !item.LockedFields.Contains(MetadataFields.Backdrops))
             {
             {
@@ -221,18 +226,15 @@ namespace MediaBrowser.Providers.Movies
                 {
                 {
                     var url = eligibleBackdrops[i].Url;
                     var url = eligibleBackdrops[i].Url;
 
 
-                    if (!item.ContainsImageWithSourceUrl(url))
+                    var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
                     {
                     {
-                        var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
-                        {
-                            Url = url,
-                            CancellationToken = cancellationToken
+                        Url = url,
+                        CancellationToken = cancellationToken
 
 
-                        }).ConfigureAwait(false);
+                    }).ConfigureAwait(false);
 
 
-                        await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Backdrop, null, url, cancellationToken)
-                          .ConfigureAwait(false);
-                    }
+                    await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Backdrop, null, url, cancellationToken)
+                      .ConfigureAwait(false);
 
 
                     if (item.BackdropImagePaths.Count >= backdropLimit)
                     if (item.BackdropImagePaths.Count >= backdropLimit)
                     {
                     {

+ 1 - 1
MediaBrowser.Providers/Movies/MovieDbProvider.cs

@@ -285,7 +285,7 @@ namespace MediaBrowser.Providers.Movies
         /// <returns><c>true</c> if [has alt meta] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [has alt meta] [the specified item]; otherwise, <c>false</c>.</returns>
         internal static bool HasAltMeta(BaseItem item)
         internal static bool HasAltMeta(BaseItem item)
         {
         {
-            var path = MovieXmlSaver.GetMovieSavePath(item);
+            var path = MovieXmlSaver.GetMovieSavePath((Video)item);
 
 
             if (item.LocationType == LocationType.FileSystem)
             if (item.LocationType == LocationType.FileSystem)
             {
             {

+ 4 - 4
MediaBrowser.Providers/Movies/MovieProviderFromXml.cs

@@ -63,7 +63,7 @@ namespace MediaBrowser.Providers.Movies
 
 
         protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
         protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
         {
         {
-            var savePath = MovieXmlSaver.GetMovieSavePath(item);
+            var savePath = MovieXmlSaver.GetMovieSavePath((Video)item);
 
 
             var xml = item.ResolveArgs.GetMetaFileByPath(savePath) ?? new FileInfo(savePath);
             var xml = item.ResolveArgs.GetMetaFileByPath(savePath) ?? new FileInfo(savePath);
 
 
@@ -86,7 +86,9 @@ namespace MediaBrowser.Providers.Movies
         {
         {
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            var path = MovieXmlSaver.GetMovieSavePath(item);
+            var video = (Video)item;
+
+            var path = MovieXmlSaver.GetMovieSavePath(video);
 
 
             if (File.Exists(path))
             if (File.Exists(path))
             {
             {
@@ -94,8 +96,6 @@ namespace MediaBrowser.Providers.Movies
 
 
                 try
                 try
                 {
                 {
-                    var video = (Video)item;
-
                     await new MovieXmlParser(Logger, _itemRepo).FetchAsync(video, path, cancellationToken).ConfigureAwait(false);
                     await new MovieXmlParser(Logger, _itemRepo).FetchAsync(video, path, cancellationToken).ConfigureAwait(false);
                 }
                 }
                 finally
                 finally

+ 6 - 4
MediaBrowser.Providers/Music/AlbumMetadataService.cs

@@ -1,7 +1,9 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -13,12 +15,12 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.Music
 namespace MediaBrowser.Providers.Music
 {
 {
-    public class AlbumMetadataService : ConcreteMetadataService<MusicAlbum, AlbumId>
+    public class AlbumMetadataService : MetadataService<MusicAlbum, AlbumId>
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
-        public AlbumMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public AlbumMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
         }
         }

+ 4 - 4
MediaBrowser.Providers/Music/AlbumXmlProvider.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music
 
 
         public async Task<MetadataResult<MusicAlbum>> GetMetadata(string path, CancellationToken cancellationToken)
         public async Task<MetadataResult<MusicAlbum>> GetMetadata(string path, CancellationToken cancellationToken)
         {
         {
-            path = GetXmlPath(path);
+            path = GetXmlFile(path).FullName;
 
 
             var result = new MetadataResult<MusicAlbum>();
             var result = new MetadataResult<MusicAlbum>();
 
 
@@ -48,12 +48,12 @@ namespace MediaBrowser.Providers.Music
 
 
         public string Name
         public string Name
         {
         {
-            get { return "Media Browser Xml"; }
+            get { return "Media Browser xml"; }
         }
         }
 
 
-        protected override string GetXmlPath(string path)
+        protected override FileInfo GetXmlFile(string path)
         {
         {
-            return Path.Combine(path, "album.xml");
+            return new FileInfo(Path.Combine(path, "album.xml"));
         }
         }
     }
     }
 }
 }

+ 6 - 4
MediaBrowser.Providers/Music/ArtistMetadataService.cs

@@ -1,7 +1,9 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -13,12 +15,12 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.Music
 namespace MediaBrowser.Providers.Music
 {
 {
-    public class ArtistMetadataService : ConcreteMetadataService<MusicArtist, ItemId>
+    public class ArtistMetadataService : MetadataService<MusicArtist, ItemId>
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
-        public ArtistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public ArtistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
         }
         }

+ 4 - 4
MediaBrowser.Providers/Music/ArtistXmlProvider.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Music
 
 
         public async Task<MetadataResult<MusicArtist>> GetMetadata(string path, CancellationToken cancellationToken)
         public async Task<MetadataResult<MusicArtist>> GetMetadata(string path, CancellationToken cancellationToken)
         {
         {
-            path = GetXmlPath(path);
+            path = GetXmlFile(path).FullName;
 
 
             var result = new MetadataResult<MusicArtist>();
             var result = new MetadataResult<MusicArtist>();
 
 
@@ -48,12 +48,12 @@ namespace MediaBrowser.Providers.Music
 
 
         public string Name
         public string Name
         {
         {
-            get { return "Media Browser Xml"; }
+            get { return "Media Browser xml"; }
         }
         }
 
 
-        protected override string GetXmlPath(string path)
+        protected override FileInfo GetXmlFile(string path)
         {
         {
-            return Path.Combine(path, "artist.xml");
+            return new FileInfo(Path.Combine(path, "artist.xml"));
         }
         }
     }
     }
 }
 }

+ 1 - 1
MediaBrowser.Providers/MusicGenres/MusicGenreImageProvider.cs

@@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.MusicGenres
 
 
         public static string ProviderName
         public static string ProviderName
         {
         {
-            get { return "Media Browser"; }
+            get { return "Media Browser Images"; }
         }
         }
 
 
         public bool Supports(IHasImages item)
         public bool Supports(IHasImages item)

+ 6 - 4
MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs

@@ -1,7 +1,9 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -11,12 +13,12 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.MusicGenres
 namespace MediaBrowser.Providers.MusicGenres
 {
 {
-    public class MusicGenreMetadataService : ConcreteMetadataService<MusicGenre, ItemId>
+    public class MusicGenreMetadataService : MetadataService<MusicGenre, ItemId>
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
-        public MusicGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public MusicGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
         }
         }

+ 7 - 4
MediaBrowser.Providers/People/PersonMetadataService.cs

@@ -1,7 +1,10 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -11,12 +14,12 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.People
 namespace MediaBrowser.Providers.People
 {
 {
-    public class PersonMetadataService : ConcreteMetadataService<Person, ItemId>
+    public class PersonMetadataService : MetadataService<Person, ItemId>
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
-        public PersonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public PersonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
         }
         }

+ 4 - 4
MediaBrowser.Providers/People/PersonXmlProvider.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.People
 
 
         public async Task<MetadataResult<Person>> GetMetadata(string path, CancellationToken cancellationToken)
         public async Task<MetadataResult<Person>> GetMetadata(string path, CancellationToken cancellationToken)
         {
         {
-            path = GetXmlPath(path);
+            path = GetXmlFile(path).FullName;
 
 
             var result = new MetadataResult<Person>();
             var result = new MetadataResult<Person>();
 
 
@@ -48,12 +48,12 @@ namespace MediaBrowser.Providers.People
 
 
         public string Name
         public string Name
         {
         {
-            get { return "Media Browser Xml"; }
+            get { return "Media Browser xml"; }
         }
         }
 
 
-        protected override string GetXmlPath(string path)
+        protected override FileInfo GetXmlFile(string path)
         {
         {
-            return Path.Combine(path, "person.xml");
+            return new FileInfo(Path.Combine(path, "person.xml"));
         }
         }
     }
     }
 }
 }

+ 13 - 7
MediaBrowser.Providers/Savers/AlbumXmlSaver.cs

@@ -1,9 +1,7 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Providers.Music;
-using System;
+using MediaBrowser.Controller.Providers;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
@@ -20,13 +18,21 @@ namespace MediaBrowser.Providers.Savers
             _config = config;
             _config = config;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -46,13 +52,13 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
             builder.Append("<Item>");
             builder.Append("<Item>");
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes((MusicAlbum)item, builder);
 
 
             builder.Append("</Item>");
             builder.Append("</Item>");
 
 
@@ -66,7 +72,7 @@ namespace MediaBrowser.Providers.Savers
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
             return Path.Combine(item.Path, "album.xml");
             return Path.Combine(item.Path, "album.xml");
         }
         }

+ 13 - 4
MediaBrowser.Providers/Savers/ArtistXmlSaver.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Providers.Music;
 using MediaBrowser.Providers.Music;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -20,13 +21,21 @@ namespace MediaBrowser.Providers.Savers
             _config = config;
             _config = config;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -59,13 +68,13 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
             builder.Append("<Item>");
             builder.Append("<Item>");
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes((MusicArtist)item, builder);
 
 
             builder.Append("</Item>");
             builder.Append("</Item>");
 
 
@@ -79,7 +88,7 @@ namespace MediaBrowser.Providers.Savers
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
             return Path.Combine(item.Path, "artist.xml");
             return Path.Combine(item.Path, "artist.xml");
         }
         }

+ 13 - 4
MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs

@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
 using System.Threading;
 using System.Threading;
+using MediaBrowser.Controller.Providers;
 
 
 namespace MediaBrowser.Providers.Savers
 namespace MediaBrowser.Providers.Savers
 {
 {
@@ -18,13 +19,21 @@ namespace MediaBrowser.Providers.Savers
             _config = config;
             _config = config;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -44,13 +53,13 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
             builder.Append("<Item>");
             builder.Append("<Item>");
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes((BoxSet)item, builder);
 
 
             builder.Append("</Item>");
             builder.Append("</Item>");
 
 
@@ -64,7 +73,7 @@ namespace MediaBrowser.Providers.Savers
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
             return Path.Combine(item.Path, "collection.xml");
             return Path.Combine(item.Path, "collection.xml");
         }
         }

+ 14 - 8
MediaBrowser.Providers/Savers/ChannelXmlSaver.cs

@@ -1,8 +1,6 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Providers.LiveTv;
-using System;
+using MediaBrowser.Controller.Providers;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
@@ -21,7 +19,7 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -35,19 +33,27 @@ namespace MediaBrowser.Providers.Savers
             return false;
             return false;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Saves the specified item.
         /// Saves the specified item.
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
             builder.Append("<Item>");
             builder.Append("<Item>");
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes((LiveTvChannel)item, builder);
 
 
             builder.Append("</Item>");
             builder.Append("</Item>");
 
 
@@ -63,7 +69,7 @@ namespace MediaBrowser.Providers.Savers
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
             return Path.Combine(item.Path, "channel.xml");
             return Path.Combine(item.Path, "channel.xml");
         }
         }

+ 13 - 5
MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs

@@ -1,8 +1,8 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Providers;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
@@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -37,6 +37,14 @@ namespace MediaBrowser.Providers.Savers
             return false;
             return false;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
 
         public EpisodeXmlSaver(IServerConfigurationManager config, IItemRepository itemRepository)
         public EpisodeXmlSaver(IServerConfigurationManager config, IItemRepository itemRepository)
@@ -51,7 +59,7 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
             var episode = (Episode)item;
             var episode = (Episode)item;
 
 
@@ -112,7 +120,7 @@ namespace MediaBrowser.Providers.Savers
                 builder.Append("<FirstAired>" + SecurityElement.Escape(episode.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</FirstAired>");
                 builder.Append("<FirstAired>" + SecurityElement.Escape(episode.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</FirstAired>");
             }
             }
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes(episode, builder);
             XmlSaverHelpers.AddMediaInfo(episode, builder, _itemRepository);
             XmlSaverHelpers.AddMediaInfo(episode, builder, _itemRepository);
 
 
             builder.Append("</Item>");
             builder.Append("</Item>");
@@ -140,7 +148,7 @@ namespace MediaBrowser.Providers.Savers
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
             var filename = Path.ChangeExtension(Path.GetFileName(item.Path), ".xml");
             var filename = Path.ChangeExtension(Path.GetFileName(item.Path), ".xml");
 
 

+ 18 - 6
MediaBrowser.Providers/Savers/FolderXmlSaver.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
@@ -20,15 +21,25 @@ namespace MediaBrowser.Providers.Savers
             _config = config;
             _config = config;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
-            if (!item.IsFolder)
+            var folder = item as Folder;
+
+            if (folder == null)
             {
             {
                 return false;
                 return false;
             }
             }
@@ -40,7 +51,8 @@ namespace MediaBrowser.Providers.Savers
             if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded))
             if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded))
             {
             {
                 if (!(item is Series) && !(item is BoxSet) && !(item is MusicArtist) && !(item is MusicAlbum) &&
                 if (!(item is Series) && !(item is BoxSet) && !(item is MusicArtist) && !(item is MusicAlbum) &&
-                    !(item is Season))
+                    !(item is Season) &&
+                    !(item is GameSystem))
                 {
                 {
                     return true;
                     return true;
                 }
                 }
@@ -64,13 +76,13 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
             builder.Append("<Item>");
             builder.Append("<Item>");
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes((Folder)item, builder);
 
 
             builder.Append("</Item>");
             builder.Append("</Item>");
 
 
@@ -84,7 +96,7 @@ namespace MediaBrowser.Providers.Savers
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
             return Path.Combine(item.Path, "folder.xml");
             return Path.Combine(item.Path, "folder.xml");
         }
         }

+ 21 - 6
MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs

@@ -1,10 +1,10 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Providers.Games;
-using System;
+using MediaBrowser.Controller.Providers;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
+using System.Security;
 using System.Text;
 using System.Text;
 using System.Threading;
 using System.Threading;
 
 
@@ -19,13 +19,21 @@ namespace MediaBrowser.Providers.Savers
             _config = config;
             _config = config;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -45,13 +53,20 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
+            var gameSystem = (GameSystem)item;
+
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
             builder.Append("<Item>");
             builder.Append("<Item>");
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+            if (!string.IsNullOrEmpty(gameSystem.GameSystemName))
+            {
+                builder.Append("<GameSystem>" + SecurityElement.Escape(gameSystem.GameSystemName) + "</GameSystem>");
+            }
+
+            XmlSaverHelpers.AddCommonNodes(gameSystem, builder);
 
 
             builder.Append("</Item>");
             builder.Append("</Item>");
 
 
@@ -65,7 +80,7 @@ namespace MediaBrowser.Providers.Savers
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
             return Path.Combine(item.Path, "gamesystem.xml");
             return Path.Combine(item.Path, "gamesystem.xml");
         }
         }

+ 15 - 8
MediaBrowser.Providers/Savers/GameXmlSaver.cs

@@ -1,9 +1,8 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Providers.Movies;
-using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
@@ -25,13 +24,21 @@ namespace MediaBrowser.Providers.Savers
             _config = config;
             _config = config;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -53,7 +60,7 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
@@ -85,7 +92,7 @@ namespace MediaBrowser.Providers.Savers
                 builder.Append("<NesBoxRom>" + SecurityElement.Escape(val) + "</NesBoxRom>");
                 builder.Append("<NesBoxRom>" + SecurityElement.Escape(val) + "</NesBoxRom>");
             }
             }
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes(game, builder);
 
 
             builder.Append("</Item>");
             builder.Append("</Item>");
 
 
@@ -100,12 +107,12 @@ namespace MediaBrowser.Providers.Savers
                 });
                 });
         }
         }
 
 
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
-            return GetGameSavePath(item);
+            return GetGameSavePath((Game)item);
         }
         }
 
 
-        public static string GetGameSavePath(BaseItem item)
+        public static string GetGameSavePath(Game item)
         {
         {
             if (item.IsInMixedFolder)
             if (item.IsInMixedFolder)
             {
             {

+ 23 - 14
MediaBrowser.Providers/Savers/MovieXmlSaver.cs

@@ -10,6 +10,7 @@ using System.IO;
 using System.Security;
 using System.Security;
 using System.Text;
 using System.Text;
 using System.Threading;
 using System.Threading;
+using MediaBrowser.Controller.Providers;
 
 
 namespace MediaBrowser.Providers.Savers
 namespace MediaBrowser.Providers.Savers
 {
 {
@@ -27,13 +28,21 @@ namespace MediaBrowser.Providers.Savers
             _itemRepository = itemRepository;
             _itemRepository = itemRepository;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -48,9 +57,9 @@ namespace MediaBrowser.Providers.Savers
                 {
                 {
                     return !trailer.IsLocalTrailer;
                     return !trailer.IsLocalTrailer;
                 }
                 }
-
+                var video = item as Video;
                 // Check parent for null to avoid running this against things like video backdrops
                 // Check parent for null to avoid running this against things like video backdrops
-                return item is Video && !(item is Episode) && item.Parent != null;
+                return video != null && !(item is Episode) && video.Parent != null;
             }
             }
 
 
             return false;
             return false;
@@ -64,22 +73,24 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
+            var video = (Video)item;
+
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
             builder.Append("<Title>");
             builder.Append("<Title>");
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes(video, builder);
 
 
-            if (item.CommunityRating.HasValue)
+            if (video.CommunityRating.HasValue)
             {
             {
-                builder.Append("<IMDBrating>" + SecurityElement.Escape(item.CommunityRating.Value.ToString(UsCulture)) + "</IMDBrating>");
+                builder.Append("<IMDBrating>" + SecurityElement.Escape(video.CommunityRating.Value.ToString(UsCulture)) + "</IMDBrating>");
             }
             }
 
 
-            if (!string.IsNullOrEmpty(item.Overview))
+            if (!string.IsNullOrEmpty(video.Overview))
             {
             {
-                builder.Append("<Description><![CDATA[" + item.Overview + "]]></Description>");
+                builder.Append("<Description><![CDATA[" + video.Overview + "]]></Description>");
             }
             }
 
 
             var musicVideo = item as MusicVideo;
             var musicVideo = item as MusicVideo;
@@ -106,8 +117,6 @@ namespace MediaBrowser.Providers.Savers
                 }
                 }
             }
             }
             
             
-            var video = (Video)item;
-
             XmlSaverHelpers.AddMediaInfo(video, builder, _itemRepository);
             XmlSaverHelpers.AddMediaInfo(video, builder, _itemRepository);
 
 
             builder.Append("</Title>");
             builder.Append("</Title>");
@@ -124,12 +133,12 @@ namespace MediaBrowser.Providers.Savers
                 });
                 });
         }
         }
 
 
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
-            return GetMovieSavePath(item);
+            return GetMovieSavePath((Video)item);
         }
         }
 
 
-        public static string GetMovieSavePath(BaseItem item)
+        public static string GetMovieSavePath(Video item)
         {
         {
             if (item.IsInMixedFolder)
             if (item.IsInMixedFolder)
             {
             {

+ 15 - 8
MediaBrowser.Providers/Savers/PersonXmlSaver.cs

@@ -1,7 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Providers.Movies;
-using System;
+using MediaBrowser.Controller.Providers;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Security;
 using System.Security;
@@ -15,13 +14,21 @@ namespace MediaBrowser.Providers.Savers
     /// </summary>
     /// </summary>
     public class PersonXmlSaver : IMetadataSaver
     public class PersonXmlSaver : IMetadataSaver
     {
     {
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -41,15 +48,15 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
+            var person = (Person)item;
+
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
             builder.Append("<Item>");
             builder.Append("<Item>");
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
-
-            var person = (Person)item;
+            XmlSaverHelpers.AddCommonNodes(person, builder);
 
 
             if (!string.IsNullOrEmpty(person.PlaceOfBirth))
             if (!string.IsNullOrEmpty(person.PlaceOfBirth))
             {
             {
@@ -71,7 +78,7 @@ namespace MediaBrowser.Providers.Savers
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
             return Path.Combine(item.Path, "person.xml");
             return Path.Combine(item.Path, "person.xml");
         }
         }

+ 13 - 5
MediaBrowser.Providers/Savers/SeasonXmlSaver.cs

@@ -1,7 +1,7 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
@@ -18,13 +18,21 @@ namespace MediaBrowser.Providers.Savers
             _config = config;
             _config = config;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -44,13 +52,13 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
             var builder = new StringBuilder();
             var builder = new StringBuilder();
 
 
             builder.Append("<Item>");
             builder.Append("<Item>");
 
 
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes((Season)item, builder);
 
 
             builder.Append("</Item>");
             builder.Append("</Item>");
 
 
@@ -64,7 +72,7 @@ namespace MediaBrowser.Providers.Savers
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
             return Path.Combine(item.Path, "season.xml");
             return Path.Combine(item.Path, "season.xml");
         }
         }

+ 15 - 7
MediaBrowser.Providers/Savers/SeriesXmlSaver.cs

@@ -1,7 +1,7 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
@@ -20,13 +20,21 @@ namespace MediaBrowser.Providers.Savers
             _config = config;
             _config = config;
         }
         }
 
 
+        public string Name
+        {
+            get
+            {
+                return "Media Browser xml";
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <param name="updateType">Type of the update.</param>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
         /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
+        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
         {
         {
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
             var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload;
@@ -46,7 +54,7 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        public void Save(BaseItem item, CancellationToken cancellationToken)
+        public void Save(IHasMetadata item, CancellationToken cancellationToken)
         {
         {
             var series = (Series)item;
             var series = (Series)item;
 
 
@@ -73,7 +81,7 @@ namespace MediaBrowser.Providers.Savers
 
 
             if (series.Studios.Count > 0)
             if (series.Studios.Count > 0)
             {
             {
-                builder.Append("<Network>" + SecurityElement.Escape(item.Studios[0]) + "</Network>");
+                builder.Append("<Network>" + SecurityElement.Escape(series.Studios[0]) + "</Network>");
             }
             }
 
 
             if (!string.IsNullOrEmpty(series.AirTime))
             if (!string.IsNullOrEmpty(series.AirTime))
@@ -97,8 +105,8 @@ namespace MediaBrowser.Providers.Savers
             {
             {
                 builder.Append("<FirstAired>" + SecurityElement.Escape(series.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</FirstAired>");
                 builder.Append("<FirstAired>" + SecurityElement.Escape(series.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</FirstAired>");
             }
             }
-            
-            XmlSaverHelpers.AddCommonNodes(item, builder);
+
+            XmlSaverHelpers.AddCommonNodes(series, builder);
 
 
             builder.Append("</Series>");
             builder.Append("</Series>");
 
 
@@ -124,7 +132,7 @@ namespace MediaBrowser.Providers.Savers
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item)
+        public string GetSavePath(IHasMetadata item)
         {
         {
             return Path.Combine(item.Path, "series.xml");
             return Path.Combine(item.Path, "series.xml");
         }
         }

+ 7 - 4
MediaBrowser.Providers/Studios/StudioMetadataService.cs

@@ -1,7 +1,10 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -11,12 +14,12 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.Studios
 namespace MediaBrowser.Providers.Studios
 {
 {
-    public class StudioMetadataService : ConcreteMetadataService<Studio, ItemId>
+    public class StudioMetadataService : MetadataService<Studio, ItemId>
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
 
 
-        public StudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public StudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
         }
         }

+ 1 - 1
MediaBrowser.Providers/Studios/StudiosImageProvider.cs

@@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.Studios
 
 
         public static string ProviderName
         public static string ProviderName
         {
         {
-            get { return "Media Browser"; }
+            get { return "Media Browser Images"; }
         }
         }
 
 
         public bool Supports(IHasImages item)
         public bool Supports(IHasImages item)

+ 4 - 1
MediaBrowser.Providers/TV/FanArtSeasonProvider.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
@@ -121,7 +122,9 @@ namespace MediaBrowser.Providers.TV
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         private async Task FetchImages(Season season, List<RemoteImageInfo> images, CancellationToken cancellationToken)
         private async Task FetchImages(Season season, List<RemoteImageInfo> images, CancellationToken cancellationToken)
         {
         {
-            if (ConfigurationManager.Configuration.DownloadSeasonImages.Thumb && !season.HasImage(ImageType.Thumb) && !season.LockedFields.Contains(MetadataFields.Images))
+            var options = ConfigurationManager.Configuration.GetMetadataOptions("Season") ?? new MetadataOptions();
+
+            if (options.IsEnabled(ImageType.Thumb) && !season.HasImage(ImageType.Thumb) && !season.LockedFields.Contains(MetadataFields.Images))
             {
             {
                 await SaveImage(season, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
                 await SaveImage(season, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
             }
             }

+ 10 - 7
MediaBrowser.Providers/TV/FanArtTVProvider.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
@@ -197,39 +198,41 @@ namespace MediaBrowser.Providers.TV
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         private async Task FetchFromXml(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
         private async Task FetchFromXml(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
         {
         {
+            var options = ConfigurationManager.Configuration.GetMetadataOptions("Series") ?? new MetadataOptions();
+            
             if (!item.LockedFields.Contains(MetadataFields.Images))
             if (!item.LockedFields.Contains(MetadataFields.Images))
             {
             {
                 cancellationToken.ThrowIfCancellationRequested();
                 cancellationToken.ThrowIfCancellationRequested();
 
 
-                if (ConfigurationManager.Configuration.DownloadSeriesImages.Primary && !item.HasImage(ImageType.Primary))
+                if (options.IsEnabled(ImageType.Primary) && !item.HasImage(ImageType.Primary))
                 {
                 {
                     await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
                     await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
                 }
                 }
 
 
                 cancellationToken.ThrowIfCancellationRequested();
                 cancellationToken.ThrowIfCancellationRequested();
 
 
-                if (ConfigurationManager.Configuration.DownloadSeriesImages.Logo && !item.HasImage(ImageType.Logo))
+                if (options.IsEnabled(ImageType.Logo) && !item.HasImage(ImageType.Logo))
                 {
                 {
                     await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
                     await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
                 }
                 }
 
 
                 cancellationToken.ThrowIfCancellationRequested();
                 cancellationToken.ThrowIfCancellationRequested();
 
 
-                if (ConfigurationManager.Configuration.DownloadSeriesImages.Art && !item.HasImage(ImageType.Art))
+                if (options.IsEnabled(ImageType.Art) && !item.HasImage(ImageType.Art))
                 {
                 {
                     await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
                     await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
                 }
                 }
 
 
                 cancellationToken.ThrowIfCancellationRequested();
                 cancellationToken.ThrowIfCancellationRequested();
 
 
-                if (ConfigurationManager.Configuration.DownloadSeriesImages.Thumb && !item.HasImage(ImageType.Thumb))
+                if (options.IsEnabled(ImageType.Thumb) && !item.HasImage(ImageType.Thumb))
                 {
                 {
                     await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
                     await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
                 }
                 }
 
 
                 cancellationToken.ThrowIfCancellationRequested();
                 cancellationToken.ThrowIfCancellationRequested();
 
 
-                if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
+                if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
                 {
                 {
                     await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
                     await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
                 }
                 }
@@ -239,8 +242,8 @@ namespace MediaBrowser.Providers.TV
             {
             {
                 cancellationToken.ThrowIfCancellationRequested();
                 cancellationToken.ThrowIfCancellationRequested();
 
 
-                var backdropLimit = ConfigurationManager.Configuration.TvOptions.MaxBackdrops;
-                if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops &&
+                var backdropLimit = options.GetLimit(ImageType.Backdrop);
+                if (options.IsEnabled(ImageType.Backdrop) &&
                     item.BackdropImagePaths.Count < backdropLimit)
                     item.BackdropImagePaths.Count < backdropLimit)
                 {
                 {
                     foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
                     foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))

+ 8 - 11
MediaBrowser.Providers/TV/TvdbSeasonProvider.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
@@ -149,16 +150,17 @@ namespace MediaBrowser.Providers.TV
 
 
             var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualTvdbSeasonImageProvider.ProviderName).ConfigureAwait(false);
             var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualTvdbSeasonImageProvider.ProviderName).ConfigureAwait(false);
 
 
-            const int backdropLimit = 1;
-
-            await DownloadImages(item, images.ToList(), backdropLimit, cancellationToken).ConfigureAwait(false);
+            await DownloadImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
 
 
             SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
             SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
             return true;
             return true;
         }
         }
 
 
-        private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, int backdropLimit, CancellationToken cancellationToken)
+        private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
         {
         {
+            var options = ConfigurationManager.Configuration.GetMetadataOptions("Season") ?? new MetadataOptions();
+            var backdropLimit = options.GetLimit(ImageType.Backdrop);
+
             if (!item.LockedFields.Contains(MetadataFields.Images))
             if (!item.LockedFields.Contains(MetadataFields.Images))
             {
             {
                 if (!item.HasImage(ImageType.Primary))
                 if (!item.HasImage(ImageType.Primary))
@@ -166,23 +168,18 @@ namespace MediaBrowser.Providers.TV
                     await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
                     await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
                 }
                 }
 
 
-                if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && !item.HasImage(ImageType.Banner))
+                if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
                 {
                 {
                     await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
                     await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
-            if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
+            if (options.IsEnabled(ImageType.Backdrop) && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
             {
             {
                 foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop))
                 foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop))
                 {
                 {
                     var url = backdrop.Url;
                     var url = backdrop.Url;
 
 
-                    if (item.ContainsImageWithSourceUrl(url))
-                    {
-                        continue;
-                    }
-
                     await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, null, cancellationToken).ConfigureAwait(false);
                     await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, null, cancellationToken).ConfigureAwait(false);
 
 
                     if (item.BackdropImagePaths.Count >= backdropLimit) break;
                     if (item.BackdropImagePaths.Count >= backdropLimit) break;

+ 9 - 9
MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
@@ -137,7 +138,9 @@ namespace MediaBrowser.Providers.TV
 
 
         protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
         protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
         {
         {
-            if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.TvOptions.MaxBackdrops)
+            var options = ConfigurationManager.Configuration.GetMetadataOptions("Series") ?? new MetadataOptions();
+            
+            if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count >= options.GetLimit(ImageType.Backdrop))
             {
             {
                 return false;
                 return false;
             }
             }
@@ -167,6 +170,8 @@ namespace MediaBrowser.Providers.TV
 
 
         private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, int backdropLimit, CancellationToken cancellationToken)
         private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, int backdropLimit, CancellationToken cancellationToken)
         {
         {
+            var options = ConfigurationManager.Configuration.GetMetadataOptions("Series") ?? new MetadataOptions();
+            
             if (!item.LockedFields.Contains(MetadataFields.Images))
             if (!item.LockedFields.Contains(MetadataFields.Images))
             {
             {
                 if (!item.HasImage(ImageType.Primary))
                 if (!item.HasImage(ImageType.Primary))
@@ -180,7 +185,7 @@ namespace MediaBrowser.Providers.TV
                     }
                     }
                 }
                 }
 
 
-                if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
+                if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
                 {
                 {
                     var image = images.FirstOrDefault(i => i.Type == ImageType.Banner);
                     var image = images.FirstOrDefault(i => i.Type == ImageType.Banner);
 
 
@@ -192,19 +197,14 @@ namespace MediaBrowser.Providers.TV
                 }
                 }
             }
             }
 
 
-            if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
+            if (options.IsEnabled(ImageType.Backdrop) && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
             {
             {
                 foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop && 
                 foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop && 
                     (!i.Width.HasValue || 
                     (!i.Width.HasValue || 
-                    i.Width.Value >= ConfigurationManager.Configuration.TvOptions.MinBackdropWidth)))
+                    i.Width.Value >= options.GetMinWidth(ImageType.Backdrop))))
                 {
                 {
                     var url = backdrop.Url;
                     var url = backdrop.Url;
 
 
-                    if (item.ContainsImageWithSourceUrl(url))
-                    {
-                        continue;
-                    }
-
                     await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, null, cancellationToken).ConfigureAwait(false);
                     await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, null, cancellationToken).ConfigureAwait(false);
 
 
                     if (item.BackdropImagePaths.Count >= backdropLimit) break;
                     if (item.BackdropImagePaths.Count >= backdropLimit) break;

+ 8 - 4
MediaBrowser.Providers/Users/UserMetadataService.cs

@@ -1,7 +1,10 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using MediaBrowser.Providers.Manager;
@@ -11,12 +14,12 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Providers.Users
 namespace MediaBrowser.Providers.Users
 {
 {
-    public class UserMetadataService : ConcreteMetadataService<User, ItemId>
+    public class UserMetadataService : MetadataService<User, ItemId>
     {
     {
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
 
 
-        public UserMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager, IUserManager userManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        public UserMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserManager userManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
         {
             _userManager = userManager;
             _userManager = userManager;
         }
         }
@@ -28,6 +31,7 @@ namespace MediaBrowser.Providers.Users
         /// <param name="target">The target.</param>
         /// <param name="target">The target.</param>
         /// <param name="lockedFields">The locked fields.</param>
         /// <param name="lockedFields">The locked fields.</param>
         /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
         /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
         protected override void MergeData(User source, User target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         protected override void MergeData(User source, User target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         {
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);

+ 7 - 51
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -9,6 +9,7 @@ using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
@@ -135,9 +136,8 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <value>The by reference items.</value>
         /// <value>The by reference items.</value>
         private ConcurrentDictionary<Guid, BaseItem> ByReferenceItems { get; set; }
         private ConcurrentDictionary<Guid, BaseItem> ByReferenceItems { get; set; }
 
 
-        private IEnumerable<IMetadataSaver> _savers;
-
         private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
         private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
+        private readonly Func<IProviderManager> _providerManagerFactory;
 
 
         /// <summary>
         /// <summary>
         /// The _library items cache
         /// The _library items cache
@@ -180,7 +180,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <param name="userManager">The user manager.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="userDataRepository">The user data repository.</param>
         /// <param name="userDataRepository">The user data repository.</param>
-        public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem)
+        public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem, Func<IProviderManager> providerManagerFactory)
         {
         {
             _logger = logger;
             _logger = logger;
             _taskManager = taskManager;
             _taskManager = taskManager;
@@ -189,6 +189,7 @@ namespace MediaBrowser.Server.Implementations.Library
             _userDataRepository = userDataRepository;
             _userDataRepository = userDataRepository;
             _libraryMonitorFactory = libraryMonitorFactory;
             _libraryMonitorFactory = libraryMonitorFactory;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
+            _providerManagerFactory = providerManagerFactory;
             ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>();
             ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>();
 
 
             ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
             ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
@@ -215,8 +216,7 @@ namespace MediaBrowser.Server.Implementations.Library
             IEnumerable<IBaseItemComparer> itemComparers,
             IEnumerable<IBaseItemComparer> itemComparers,
             IEnumerable<ILibraryPrescanTask> prescanTasks,
             IEnumerable<ILibraryPrescanTask> prescanTasks,
             IEnumerable<ILibraryPostScanTask> postscanTasks,
             IEnumerable<ILibraryPostScanTask> postscanTasks,
-            IEnumerable<IPeoplePrescanTask> peoplePrescanTasks,
-            IEnumerable<IMetadataSaver> savers)
+            IEnumerable<IPeoplePrescanTask> peoplePrescanTasks)
         {
         {
             EntityResolutionIgnoreRules = rules.ToArray();
             EntityResolutionIgnoreRules = rules.ToArray();
             PluginFolderCreators = pluginFolders.ToArray();
             PluginFolderCreators = pluginFolders.ToArray();
@@ -226,7 +226,6 @@ namespace MediaBrowser.Server.Implementations.Library
             PrescanTasks = prescanTasks;
             PrescanTasks = prescanTasks;
             PostscanTasks = postscanTasks;
             PostscanTasks = postscanTasks;
             PeoplePrescanTasks = peoplePrescanTasks;
             PeoplePrescanTasks = peoplePrescanTasks;
-            _savers = savers;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -1375,9 +1374,9 @@ namespace MediaBrowser.Server.Implementations.Library
         {
         {
             if (item.LocationType == LocationType.FileSystem)
             if (item.LocationType == LocationType.FileSystem)
             {
             {
-                await SaveMetadata(item, updateReason).ConfigureAwait(false);
+                await _providerManagerFactory().SaveMetadata(item, updateReason).ConfigureAwait(false);
             }
             }
-
+            
             item.DateLastSaved = DateTime.UtcNow;
             item.DateLastSaved = DateTime.UtcNow;
 
 
             await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false);
             await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false);
@@ -1440,49 +1439,6 @@ namespace MediaBrowser.Server.Implementations.Library
             return ItemRepository.RetrieveItem(id);
             return ItemRepository.RetrieveItem(id);
         }
         }
 
 
-        private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
-
-        /// <summary>
-        /// Saves the metadata.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="updateType">Type of the update.</param>
-        /// <returns>Task.</returns>
-        public async Task SaveMetadata(BaseItem item, ItemUpdateType updateType)
-        {
-            var locationType = item.LocationType;
-            if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
-            {
-                throw new ArgumentException("Only file-system based items can save metadata.");
-            }
-
-            foreach (var saver in _savers.Where(i => i.IsEnabledFor(item, updateType)))
-            {
-                var path = saver.GetSavePath(item);
-
-                var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1));
-
-                var libraryMonitor = _libraryMonitorFactory();
-
-                await semaphore.WaitAsync().ConfigureAwait(false);
-
-                try
-                {
-                    libraryMonitor.ReportFileSystemChangeBeginning(path);
-                    saver.Save(item, CancellationToken.None);
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error in metadata saver", ex);
-                }
-                finally
-                {
-                    libraryMonitor.ReportFileSystemChangeComplete(path, false);
-                    semaphore.Release();
-                }
-            }
-        }
-
         /// <summary>
         /// <summary>
         /// Finds the type of the collection.
         /// Finds the type of the collection.
         /// </summary>
         /// </summary>

+ 4 - 4
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -282,7 +282,7 @@ namespace MediaBrowser.ServerApplication
             UserManager = new UserManager(Logger, ServerConfigurationManager, UserRepository);
             UserManager = new UserManager(Logger, ServerConfigurationManager, UserRepository);
             RegisterSingleInstance(UserManager);
             RegisterSingleInstance(UserManager);
 
 
-            LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager);
+            LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager);
             RegisterSingleInstance(LibraryManager);
             RegisterSingleInstance(LibraryManager);
 
 
             LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager);
             LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager);
@@ -499,10 +499,10 @@ namespace MediaBrowser.ServerApplication
                                     GetExports<IBaseItemComparer>(),
                                     GetExports<IBaseItemComparer>(),
                                     GetExports<ILibraryPrescanTask>(),
                                     GetExports<ILibraryPrescanTask>(),
                                     GetExports<ILibraryPostScanTask>(),
                                     GetExports<ILibraryPostScanTask>(),
-                                    GetExports<IPeoplePrescanTask>(),
-                                    GetExports<IMetadataSaver>());
+                                    GetExports<IPeoplePrescanTask>());
 
 
-            ProviderManager.AddParts(GetExports<BaseMetadataProvider>(), GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>());
+            ProviderManager.AddParts(GetExports<BaseMetadataProvider>(), GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(),
+                                    GetExports<IMetadataSaver>());
 
 
             ImageProcessor.AddParts(GetExports<IImageEnhancer>());
             ImageProcessor.AddParts(GetExports<IImageEnhancer>());
 
 

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.310</version>
+        <version>3.0.317</version>
         <title>MediaBrowser.Common.Internal</title>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
         <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.310" />
+            <dependency id="MediaBrowser.Common" version="3.0.317" />
             <dependency id="NLog" version="2.1.0" />
             <dependency id="NLog" version="2.1.0" />
             <dependency id="SimpleInjector" version="2.4.1" />
             <dependency id="SimpleInjector" version="2.4.1" />
             <dependency id="sharpcompress" version="0.10.2" />
             <dependency id="sharpcompress" version="0.10.2" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Common</id>
         <id>MediaBrowser.Common</id>
-        <version>3.0.310</version>
+        <version>3.0.317</version>
         <title>MediaBrowser.Common</title>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Server.Core</id>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.310</version>
+        <version>3.0.317</version>
         <title>Media Browser.Server.Core</title>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.310" />
+            <dependency id="MediaBrowser.Common" version="3.0.317" />
         </dependencies>
         </dependencies>
     </metadata>
     </metadata>
     <files>
     <files>