Browse Source

convert album providers

Luke Pulverenti 11 năm trước cách đây
mục cha
commit
67fde8c16d
52 tập tin đã thay đổi với 1145 bổ sung1535 xóa
  1. 0 3
      MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
  2. 2 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  3. 0 6
      MediaBrowser.Controller/Providers/IImageProvider.cs
  4. 27 0
      MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs
  5. 5 28
      MediaBrowser.Controller/Providers/IMetadataProvider.cs
  6. 1 2
      MediaBrowser.Controller/Providers/IMetadataService.cs
  7. 2 2
      MediaBrowser.Controller/Providers/IProviderManager.cs
  8. 21 0
      MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs
  9. 14 0
      MediaBrowser.Controller/Providers/ItemId.cs
  10. 3 1
      MediaBrowser.Providers/All/LocalImageProvider.cs
  11. 1 1
      MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
  12. 1 1
      MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs
  13. 1 1
      MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs
  14. 1 1
      MediaBrowser.Providers/Genres/GenreMetadataService.cs
  15. 1 1
      MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs
  16. 1 1
      MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
  17. 3 2
      MediaBrowser.Providers/Manager/ConcreteMetadataService.cs
  18. 20 17
      MediaBrowser.Providers/Manager/ItemImageProvider.cs
  19. 13 66
      MediaBrowser.Providers/Manager/MetadataService.cs
  20. 84 21
      MediaBrowser.Providers/Manager/ProviderManager.cs
  21. 8 11
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  22. 4 4
      MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
  23. 5 5
      MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs
  24. 4 4
      MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs
  25. 1 1
      MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs
  26. 0 154
      MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs
  27. 188 0
      MediaBrowser.Providers/Music/AlbumMetadataService.cs
  28. 0 92
      MediaBrowser.Providers/Music/AlbumProviderFromXml.cs
  29. 59 0
      MediaBrowser.Providers/Music/AlbumXmlProvider.cs
  30. 3 3
      MediaBrowser.Providers/Music/ArtistMetadataService.cs
  31. 288 125
      MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
  32. 1 1
      MediaBrowser.Providers/Music/FanArtArtistProvider.cs
  33. 2 2
      MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs
  34. 120 68
      MediaBrowser.Providers/Music/LastFmImageProvider.cs
  35. 195 114
      MediaBrowser.Providers/Music/LastfmAlbumProvider.cs
  36. 6 22
      MediaBrowser.Providers/Music/LastfmArtistProvider.cs
  37. 0 194
      MediaBrowser.Providers/Music/LastfmBaseProvider.cs
  38. 18 32
      MediaBrowser.Providers/Music/LastfmHelper.cs
  39. 0 354
      MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs
  40. 0 150
      MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs
  41. 31 34
      MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
  42. 1 1
      MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
  43. 1 1
      MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs
  44. 1 1
      MediaBrowser.Providers/People/PersonMetadataService.cs
  45. 1 1
      MediaBrowser.Providers/People/TvdbPersonImageProvider.cs
  46. 1 1
      MediaBrowser.Providers/Studios/StudioMetadataService.cs
  47. 1 1
      MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs
  48. 1 1
      MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs
  49. 1 1
      MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
  50. 1 1
      MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs
  51. 1 1
      MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs
  52. 1 1
      MediaBrowser.Providers/Users/UserMetadataService.cs

+ 0 - 3
MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs

@@ -21,9 +21,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             Tags = new List<string>();
         }
 
-        public string LastFmImageUrl { get; set; }
-        public string LastFmImageSize { get; set; }
-
         /// <summary>
         /// Gets or sets the tags.
         /// </summary>

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

@@ -146,11 +146,13 @@
     <Compile Include="Providers\IDynamicInfoProvider.cs" />
     <Compile Include="Providers\IHasMetadata.cs" />
     <Compile Include="Providers\IImageProvider.cs" />
+    <Compile Include="Providers\ILocalMetadataProvider.cs" />
     <Compile Include="Providers\IProviderRepository.cs" />
     <Compile Include="Providers\IRemoteImageProvider.cs" />
     <Compile Include="Providers\ILocalImageProvider.cs" />
     <Compile Include="Providers\IMetadataProvider.cs" />
     <Compile Include="Providers\IMetadataService.cs" />
+    <Compile Include="Providers\IRemoteMetadataProvider.cs" />
     <Compile Include="Providers\ItemId.cs" />
     <Compile Include="Providers\MetadataRefreshOptions.cs" />
     <Compile Include="Providers\NameParser.cs" />

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

@@ -19,11 +19,5 @@ namespace MediaBrowser.Controller.Providers
         /// <param name="item">The item.</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         bool Supports(IHasImages item);
-
-        /// <summary>
-        /// Gets the order.
-        /// </summary>
-        /// <value>The order.</value>
-        int Order { get; }
     }
 }

+ 27 - 0
MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs

@@ -0,0 +1,27 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public interface ILocalMetadataProvider : IMetadataProvider
+    {
+        /// <summary>
+        /// Determines whether [has local metadata] [the specified item].
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns><c>true</c> if [has local metadata] [the specified item]; otherwise, <c>false</c>.</returns>
+        bool HasLocalMetadata(IHasMetadata item);
+    }
+
+    public interface ILocalMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ILocalMetadataProvider
+         where TItemType : IHasMetadata
+    {
+        /// <summary>
+        /// Gets the metadata.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{MetadataResult{`0}}.</returns>
+        Task<MetadataResult<TItemType>> GetMetadata(string path, CancellationToken cancellationToken);
+    }
+}

+ 5 - 28
MediaBrowser.Controller/Providers/IMetadataProvider.cs

@@ -1,6 +1,4 @@
 using System;
-using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Providers
 {
@@ -20,32 +18,6 @@ namespace MediaBrowser.Controller.Providers
            where TItemType : IHasMetadata
     {
     }
-    
-    public interface ILocalMetadataProvider : IMetadataProvider
-    {
-        /// <summary>
-        /// Determines whether [has local metadata] [the specified item].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if [has local metadata] [the specified item]; otherwise, <c>false</c>.</returns>
-        bool HasLocalMetadata(IHasMetadata item);
-    }
-
-    public interface IRemoteMetadataProvider : IMetadataProvider
-    {
-    }
-
-    public interface IRemoteMetadataProvider<TItemType> : IMetadataProvider<TItemType>, IRemoteMetadataProvider
-        where TItemType : IHasMetadata
-    {
-        Task<MetadataResult<TItemType>> GetMetadata(ItemId id, CancellationToken cancellationToken);
-    }
-
-    public interface ILocalMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ILocalMetadataProvider
-         where TItemType : IHasMetadata
-    {
-        Task<MetadataResult<TItemType>> GetMetadata(string path, CancellationToken cancellationToken);
-    }
 
     public interface IHasChangeMonitor
     {
@@ -58,6 +30,11 @@ namespace MediaBrowser.Controller.Providers
         bool HasChanged(IHasMetadata item, DateTime date);
     }
 
+    public interface IHasOrder
+    {
+        int Order { get; }
+    }
+
     public class MetadataResult<T>
         where T : IHasMetadata
     {

+ 1 - 2
MediaBrowser.Controller/Providers/IMetadataService.cs

@@ -10,8 +10,7 @@ namespace MediaBrowser.Controller.Providers
         /// Adds the parts.
         /// </summary>
         /// <param name="providers">The providers.</param>
-        /// <param name="imageProviders">The image providers.</param>
-        void AddParts(IEnumerable<IMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders);
+        void AddParts(IEnumerable<IMetadataProvider> providers);
 
         /// <summary>
         /// Determines whether this instance can refresh the specified item.

+ 2 - 2
MediaBrowser.Controller/Providers/IProviderManager.cs

@@ -74,13 +74,13 @@ namespace MediaBrowser.Controller.Providers
         /// <param name="providerName">Name of the provider.</param>
         /// <param name="type">The type.</param>
         /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
-        Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, CancellationToken cancellationToken, string providerName = null, ImageType? type = null);
+        Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(IHasImages item, CancellationToken cancellationToken, string providerName = null, ImageType? type = null);
 
         /// <summary>
         /// Gets the image providers.
         /// </summary>
         /// <param name="item">The item.</param>
         /// <returns>IEnumerable{ImageProviderInfo}.</returns>
-        IEnumerable<ImageProviderInfo> GetImageProviderInfo(BaseItem item);
+        IEnumerable<ImageProviderInfo> GetImageProviderInfo(IHasImages item);
     }
 }

+ 21 - 0
MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs

@@ -0,0 +1,21 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public interface IRemoteMetadataProvider : IMetadataProvider
+    {
+    }
+
+    public interface IRemoteMetadataProvider<TItemType> : IMetadataProvider<TItemType>, IRemoteMetadataProvider
+        where TItemType : IHasMetadata
+    {
+        /// <summary>
+        /// Gets the metadata.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{MetadataResult{`0}}.</returns>
+        Task<MetadataResult<TItemType>> GetMetadata(ItemId id, CancellationToken cancellationToken);
+    }
+}

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

@@ -37,4 +37,18 @@ namespace MediaBrowser.Controller.Providers
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }
     }
+
+    public class AlbumId : ItemId
+    {
+        /// <summary>
+        /// Gets or sets the album artist.
+        /// </summary>
+        /// <value>The album artist.</value>
+        public string AlbumArtist { get; set; }
+        /// <summary>
+        /// Gets or sets the artist music brainz identifier.
+        /// </summary>
+        /// <value>The artist music brainz identifier.</value>
+        public string ArtistMusicBrainzId { get; set; }
+    }
 }

+ 3 - 1
MediaBrowser.Providers/All/LocalImageProvider.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Providers;
@@ -38,13 +39,14 @@ namespace MediaBrowser.Providers.All
             if (locationType == LocationType.FileSystem)
             {
                 // Episode has it's own provider
-                if (item is Episode)
+                if (item is Episode || item is Audio)
                 {
                     return false;
                 }
 
                 return true;
             }
+
             if (locationType == LocationType.Virtual)
             {
                 var season = item as Season;

+ 1 - 1
MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs

@@ -15,7 +15,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.BoxSets
 {
-    public class BoxSetMetadataService : ConcreteMetadataService<BoxSet>
+    public class BoxSetMetadataService : ConcreteMetadataService<BoxSet, ItemId>
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILocalizationManager _iLocalizationManager;

+ 1 - 1
MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs

@@ -14,7 +14,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.BoxSets
 {
-    class MovieDbBoxSetImageProvider : IRemoteImageProvider
+    class MovieDbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
     {
         private readonly IHttpClient _httpClient;
 

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

@@ -11,7 +11,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.GameGenres
 {
-    public class GameGenreMetadataService : ConcreteMetadataService<GameGenre>
+    public class GameGenreMetadataService : ConcreteMetadataService<GameGenre, ItemId>
     {
         private readonly ILibraryManager _libraryManager;
 

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

@@ -11,7 +11,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Genres
 {
-    public class GenreMetadataService : ConcreteMetadataService<Genre>
+    public class GenreMetadataService : ConcreteMetadataService<Genre, ItemId>
     {
         private readonly ILibraryManager _libraryManager;
 

+ 1 - 1
MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs

@@ -11,7 +11,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.LiveTv
 {
-    public class ChannelMetadataService : ConcreteMetadataService<LiveTvChannel>
+    public class ChannelMetadataService : ConcreteMetadataService<LiveTvChannel, ItemId>
     {
         private readonly ILibraryManager _libraryManager;
 

+ 1 - 1
MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs

@@ -11,7 +11,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.LiveTv
 {
-    public class ProgramMetadataService : ConcreteMetadataService<LiveTvProgram>
+    public class ProgramMetadataService : ConcreteMetadataService<LiveTvProgram, ItemId>
     {
         private readonly ILibraryManager _libraryManager;
 

+ 3 - 2
MediaBrowser.Providers/Manager/ConcreteMetadataService.cs

@@ -4,8 +4,9 @@ using MediaBrowser.Model.Logging;
 
 namespace MediaBrowser.Providers.Manager
 {
-    public abstract class ConcreteMetadataService<TItemType> : MetadataService<TItemType>
-           where TItemType : IHasMetadata, new()
+    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)

+ 20 - 17
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Common.Extensions;
+using System.IO;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
@@ -21,6 +23,7 @@ namespace MediaBrowser.Providers.Manager
         private readonly ILogger _logger;
         private readonly IProviderManager _providerManager;
         private readonly IServerConfigurationManager _config;
+        private readonly IFileSystem _fileSystem;
 
         public ItemImageProvider(ILogger logger, IProviderManager providerManager, IServerConfigurationManager config)
         {
@@ -97,9 +100,21 @@ namespace MediaBrowser.Providers.Manager
 
                         if (response.HasImage)
                         {
-                            var mimeType = "image/" + response.Format.ToString().ToLower();
+                            if (!string.IsNullOrEmpty(response.Path))
+                            {
+                                var mimeType = "image/" + Path.GetExtension(response.Path).TrimStart('.').ToLower();
 
-                            await _providerManager.SaveImage((BaseItem)item, response.Stream, mimeType, imageType, null, Guid.NewGuid().ToString(), cancellationToken).ConfigureAwait(false);
+                                var stream = _fileSystem.GetFileStream(response.Path, FileMode.Open, FileAccess.Read,
+                                    FileShare.Read, true);
+
+                                await _providerManager.SaveImage((BaseItem)item, stream, mimeType, imageType, null, Guid.NewGuid().ToString(), cancellationToken).ConfigureAwait(false);
+                            }
+                            else
+                            {
+                                var mimeType = "image/" + response.Format.ToString().ToLower();
+
+                                await _providerManager.SaveImage((BaseItem)item, response.Stream, mimeType, imageType, null, Guid.NewGuid().ToString(), cancellationToken).ConfigureAwait(false);
+                            }
 
                             result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
                         }
@@ -227,26 +242,14 @@ namespace MediaBrowser.Providers.Manager
         /// <returns>IEnumerable{IImageProvider}.</returns>
         private IEnumerable<IImageProvider> GetImageProviders(IHasImages item, IEnumerable<IImageProvider> imageProviders)
         {
-            var providers = imageProviders.Where(i =>
-            {
-                try
-                {
-                    return i.Supports(item);
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error in ImageProvider.Supports", ex, i.Name);
-
-                    return false;
-                }
-            });
+            var providers = imageProviders;
 
             if (!_config.Configuration.EnableInternetProviders)
             {
                 providers = providers.Where(i => !(i is IRemoteImageProvider));
             }
 
-            return providers.OrderBy(i => i.Order);
+            return providers;
         }
 
         private bool MergeImages(IHasImages item, List<LocalImageInfo> images)

+ 13 - 66
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
@@ -13,8 +12,9 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Manager
 {
-    public abstract class MetadataService<TItemType> : IMetadataService
+    public abstract class MetadataService<TItemType, TIdType> : IMetadataService
         where TItemType : IHasMetadata
+        where TIdType : ItemId, new()
     {
         protected readonly IServerConfigurationManager ServerConfigurationManager;
         protected readonly ILogger Logger;
@@ -23,8 +23,6 @@ namespace MediaBrowser.Providers.Manager
 
         private IMetadataProvider<TItemType>[] _providers = { };
 
-        private IImageProvider[] _imageProviders = { };
-
         protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo)
         {
             ServerConfigurationManager = serverConfigurationManager;
@@ -37,13 +35,10 @@ namespace MediaBrowser.Providers.Manager
         /// Adds the parts.
         /// </summary>
         /// <param name="providers">The providers.</param>
-        /// <param name="imageProviders">The image providers.</param>
-        public void AddParts(IEnumerable<IMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders)
+        public void AddParts(IEnumerable<IMetadataProvider> providers)
         {
             _providers = providers.OfType<IMetadataProvider<TItemType>>()
                 .ToArray();
-
-            _imageProviders = imageProviders.OrderBy(i => i.Order).ToArray();
         }
 
         /// <summary>
@@ -79,11 +74,13 @@ namespace MediaBrowser.Providers.Manager
             var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager);
             var localImagesFailed = false;
 
+            var allImageProviders = ((ProviderManager)ProviderManager).GetImageProviders(item).ToList();
+
             // Start by validating images
             try
             {
                 // Always validate images and check for new locally stored ones.
-                if (itemImageProvider.ValidateImages(item, GetLocalImageProviders(item)))
+                if (itemImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>()))
                 {
                     updateType = updateType | ItemUpdateType.ImageUpdate;
                 }
@@ -114,7 +111,7 @@ namespace MediaBrowser.Providers.Manager
             // Next run remote image providers, but only if local image providers didn't throw an exception
             if (!localImagesFailed && options.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
             {
-                var providers = GetNonLocalImageProviders(item, lastResult.DateLastImagesRefresh.HasValue, options).ToList();
+                var providers = GetNonLocalImageProviders(item, allImageProviders, lastResult.DateLastImagesRefresh.HasValue, options).ToList();
 
                 if (providers.Count > 0)
                 {
@@ -135,7 +132,7 @@ namespace MediaBrowser.Providers.Manager
             {
                 if (string.IsNullOrEmpty(item.Name))
                 {
-                    throw new InvalidOperationException("Item has no name");
+                    throw new InvalidOperationException(item.GetType().Name + " has no name: " + item.Path);
                 }
 
                 // Save to database
@@ -167,7 +164,7 @@ namespace MediaBrowser.Providers.Manager
         protected virtual IEnumerable<IMetadataProvider> GetProviders(IHasMetadata item, bool hasRefreshedMetadata, MetadataRefreshOptions options)
         {
             // Get providers to refresh
-            var providers = _providers.Where(i => CanRefresh(i, item)).ToList();
+            var providers = ((ProviderManager) ProviderManager).GetMetadataProviders<TItemType>(item).ToList();
 
             // Run all if either of these flags are true
             var runAllProviders = options.ReplaceAllMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || !hasRefreshedMetadata;
@@ -193,22 +190,10 @@ namespace MediaBrowser.Providers.Manager
             return providers;
         }
 
-        protected virtual IEnumerable<IImageProvider> GetNonLocalImageProviders(IHasMetadata item, bool hasRefreshedImages, ImageRefreshOptions options)
+        protected virtual IEnumerable<IImageProvider> GetNonLocalImageProviders(IHasMetadata item, IEnumerable<IImageProvider> allImageProviders, bool hasRefreshedImages, ImageRefreshOptions options)
         {
             // Get providers to refresh
-            var providers = _imageProviders.Where(i =>
-            {
-                try
-                {
-                    return !(i is ILocalImageProvider) && i.Supports(item);
-                }
-                catch (Exception ex)
-                {
-                    Logger.ErrorException("Error in ImageProvider.Supports", ex, i.Name);
-
-                    return false;
-                }
-            }).ToList();
+            var providers = allImageProviders.Where(i => !(i is ILocalImageProvider)).ToList();
 
             // Run all if either of these flags are true
             var runAllProviders = options.ImageRefreshMode == ImageRefreshMode.FullRefresh || !hasRefreshedImages;
@@ -226,33 +211,12 @@ namespace MediaBrowser.Providers.Manager
 
             return providers;
         }
-        
-        /// <summary>
-        /// Determines whether this instance can refresh the specified provider.
-        /// </summary>
-        /// <param name="provider">The provider.</param>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if this instance can refresh the specified provider; otherwise, <c>false</c>.</returns>
-        protected bool CanRefresh(IMetadataProvider provider, IHasMetadata item)
-        {
-            if (!ServerConfigurationManager.Configuration.EnableInternetProviders && provider is IRemoteMetadataProvider)
-            {
-                return false;
-            }
-
-            if (item.LocationType != LocationType.FileSystem && provider is ILocalMetadataProvider)
-            {
-                return false;
-            }
-
-            return true;
-        }
 
         protected abstract Task SaveItem(TItemType item, ItemUpdateType reason, CancellationToken cancellationToken);
 
-        protected virtual ItemId GetId(IHasMetadata item)
+        protected virtual TIdType GetId(TItemType item)
         {
-            return new ItemId
+            return new TIdType
             {
                 MetadataCountryCode = item.GetPreferredMetadataCountryCode(),
                 MetadataLanguage = item.GetPreferredMetadataLanguage(),
@@ -371,23 +335,6 @@ namespace MediaBrowser.Providers.Manager
                 return 0;
             }
         }
-
-        private IEnumerable<ILocalImageProvider> GetLocalImageProviders(IHasImages item)
-        {
-            return _imageProviders.OfType<ILocalImageProvider>().Where(i =>
-            {
-                try
-                {
-                    return i.Supports(item);
-                }
-                catch (Exception ex)
-                {
-                    Logger.ErrorException("Error in ImageProvider.Supports", ex, i.Name);
-
-                    return false;
-                }
-            });
-        }
     }
 
     public class RefreshResult

+ 84 - 21
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -50,7 +50,6 @@ namespace MediaBrowser.Providers.Manager
         /// <value>The metadata providers enumerable.</value>
         private BaseMetadataProvider[] MetadataProviders { get; set; }
 
-        private IRemoteImageProvider[] RemoteImageProviders { get; set; }
         private IImageProvider[] ImageProviders { get; set; }
 
         private readonly IFileSystem _fileSystem;
@@ -58,6 +57,7 @@ namespace MediaBrowser.Providers.Manager
         private readonly IProviderRepository _providerRepo;
 
         private IMetadataService[] _metadataServices = { };
+        private IMetadataProvider[] _metadataProviders = { };
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ProviderManager" /> class.
@@ -89,16 +89,10 @@ namespace MediaBrowser.Providers.Manager
         {
             MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
 
-            ImageProviders = imageProviders.OrderBy(i => i.Order).ToArray();
-            RemoteImageProviders = ImageProviders.OfType<IRemoteImageProvider>().ToArray();
+            ImageProviders = imageProviders.ToArray();
 
             _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
-
-            var providerList = metadataProviders.ToList();
-            foreach (var service in _metadataServices)
-            {
-                service.AddParts(providerList, ImageProviders);
-            }
+            _metadataProviders = metadataProviders.ToArray();
         }
 
         public Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken)
@@ -391,7 +385,7 @@ namespace MediaBrowser.Providers.Manager
         /// <param name="providerName">Name of the provider.</param>
         /// <param name="type">The type.</param>
         /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
-        public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, CancellationToken cancellationToken, string providerName = null, ImageType? type = null)
+        public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(IHasImages item, CancellationToken cancellationToken, string providerName = null, ImageType? type = null)
         {
             var providers = GetRemoteImageProviders(item);
 
@@ -418,7 +412,7 @@ namespace MediaBrowser.Providers.Manager
         /// <param name="preferredLanguage">The preferred language.</param>
         /// <param name="type">The type.</param>
         /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
-        private async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken, IRemoteImageProvider i, string preferredLanguage, ImageType? type = null)
+        private async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken, IRemoteImageProvider i, string preferredLanguage, ImageType? type = null)
         {
             try
             {
@@ -452,9 +446,23 @@ namespace MediaBrowser.Providers.Manager
             return images;
         }
 
-        private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item)
+        /// <summary>
+        /// Gets the supported image providers.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>IEnumerable{IImageProvider}.</returns>
+        public IEnumerable<ImageProviderInfo> GetImageProviderInfo(IHasImages item)
+        {
+            return GetRemoteImageProviders(item).Select(i => new ImageProviderInfo
+            {
+                Name = i.Name,
+                Order = GetOrder(item, i)
+            });
+        }
+
+        public IEnumerable<IImageProvider> GetImageProviders(IHasImages item)
         {
-            return RemoteImageProviders.Where(i =>
+            return ImageProviders.Where(i =>
             {
                 try
                 {
@@ -466,22 +474,77 @@ namespace MediaBrowser.Providers.Manager
                     return false;
                 }
 
-            });
+            }).OrderBy(i => GetOrder(item, i));
+        }
+
+        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(IHasMetadata item)
+            where T : IHasMetadata
+        {
+            return _metadataProviders.OfType<IMetadataProvider<T>>()
+                .Where(i => CanRefresh(i, item))
+                .OrderBy(i => GetOrder(item, i));
+        }
+
+        private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(IHasImages item)
+        {
+            return GetImageProviders(item).OfType<IRemoteImageProvider>();
         }
 
         /// <summary>
-        /// Gets the supported image providers.
+        /// Determines whether this instance can refresh the specified provider.
         /// </summary>
+        /// <param name="provider">The provider.</param>
         /// <param name="item">The item.</param>
-        /// <returns>IEnumerable{IImageProvider}.</returns>
-        public IEnumerable<ImageProviderInfo> GetImageProviderInfo(BaseItem item)
+        /// <returns><c>true</c> if this instance can refresh the specified provider; otherwise, <c>false</c>.</returns>
+        protected bool CanRefresh(IMetadataProvider provider, IHasMetadata item)
         {
-            return GetRemoteImageProviders(item).Select(i => new ImageProviderInfo
+            if (!ConfigurationManager.Configuration.EnableInternetProviders && provider is IRemoteMetadataProvider)
             {
-                Name = i.Name,
-                Order = i.Order
+                return false;
+            }
 
-            });
+            if (item.LocationType != LocationType.FileSystem && provider is ILocalMetadataProvider)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Gets the order.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="provider">The provider.</param>
+        /// <returns>System.Int32.</returns>
+        private int GetOrder(IHasImages item, IImageProvider provider)
+        {
+            var hasOrder = provider as IHasOrder;
+
+            if (hasOrder == null)
+            {
+                return 0;
+            }
+
+            return hasOrder.Order;
+        }
+
+        /// <summary>
+        /// Gets the order.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="provider">The provider.</param>
+        /// <returns>System.Int32.</returns>
+        private int GetOrder(IHasMetadata item, IMetadataProvider provider)
+        {
+            var hasOrder = provider as IHasOrder;
+
+            if (hasOrder == null)
+            {
+                return 0;
+            }
+
+            return hasOrder.Order;
         }
     }
 }

+ 8 - 11
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -100,29 +100,26 @@
     <Compile Include="Movies\ManualMovieDbImageProvider.cs" />
     <Compile Include="Movies\ManualFanartMovieImageProvider.cs" />
     <Compile Include="MusicGenres\MusicGenreMetadataService.cs" />
+    <Compile Include="Music\AlbumMetadataService.cs" />
     <Compile Include="Music\ArtistMetadataService.cs" />
-    <Compile Include="Music\LastFmArtistProvider.cs" />
+    <Compile Include="Music\LastfmArtistProvider.cs" />
     <Compile Include="People\MovieDbPersonImageProvider.cs" />
     <Compile Include="Movies\MovieUpdatesPrescanTask.cs" />
     <Compile Include="Movies\MovieXmlParser.cs" />
-    <Compile Include="Movies\FanArtMovieProvider.cs" />
-    <Compile Include="Movies\FanArtMovieUpdatesPrescanTask.cs" />
+    <Compile Include="Movies\FanartMovieProvider.cs" />
+    <Compile Include="Movies\FanartMovieUpdatesPrescanTask.cs" />
     <Compile Include="Movies\MovieDbImagesProvider.cs" />
     <Compile Include="Movies\MovieDbProvider.cs" />
     <Compile Include="Movies\MovieProviderFromXml.cs" />
     <Compile Include="Movies\OpenMovieDatabaseProvider.cs" />
-    <Compile Include="Music\AlbumInfoFromSongProvider.cs" />
-    <Compile Include="Music\AlbumProviderFromXml.cs" />
+    <Compile Include="Music\AlbumXmlProvider.cs" />
     <Compile Include="Music\ArtistXmlProvider.cs" />
-    <Compile Include="Music\FanArtAlbumProvider.cs" />
-    <Compile Include="Music\FanArtUpdatesPrescanTask.cs" />
+    <Compile Include="Music\FanartUpdatesPrescanTask.cs" />
     <Compile Include="Music\LastfmAlbumProvider.cs" />
-    <Compile Include="Music\LastFmImageProvider.cs" />
-    <Compile Include="Music\LastfmBaseProvider.cs" />
     <Compile Include="Music\LastfmHelper.cs" />
-    <Compile Include="Music\ManualFanartAlbumProvider.cs" />
+    <Compile Include="Music\FanartAlbumProvider.cs" />
     <Compile Include="Music\FanartArtistProvider.cs" />
-    <Compile Include="Music\ManualLastFmImageProvider.cs" />
+    <Compile Include="Music\LastfmImageProvider.cs" />
     <Compile Include="Music\MusicBrainzAlbumProvider.cs" />
     <Compile Include="Music\MusicVideoXmlParser.cs" />
     <Compile Include="Music\SoundtrackPostScanTask.cs" />

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

@@ -24,7 +24,7 @@ namespace MediaBrowser.Providers.Movies
     /// <summary>
     /// Class FanArtMovieProvider
     /// </summary>
-    class FanArtMovieProvider : BaseMetadataProvider
+    class FanartMovieProvider : BaseMetadataProvider
     {
         /// <summary>
         /// Gets the HTTP client.
@@ -37,18 +37,18 @@ namespace MediaBrowser.Providers.Movies
         /// </summary>
         private readonly IProviderManager _providerManager;
 
-        internal static FanArtMovieProvider Current { get; private set; }
+        internal static FanartMovieProvider Current { get; private set; }
         private readonly IFileSystem _fileSystem;
 
         /// <summary>
-        /// Initializes a new instance of the <see cref="FanArtMovieProvider" /> class.
+        /// Initializes a new instance of the <see cref="FanartMovieProvider" /> class.
         /// </summary>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
-        public FanArtMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
+        public FanartMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
         {
             if (httpClient == null)

+ 5 - 5
MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs

@@ -16,7 +16,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Movies
 {
-    class FanArtMovieUpdatesPrescanTask : ILibraryPostScanTask
+    class FanartMovieUpdatesPrescanTask : ILibraryPostScanTask
     {
         private const string UpdatesUrl = "http://api.fanart.tv/webservice/newmovies/{0}/{1}/";
 
@@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.Movies
 
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
-        public FanArtMovieUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
+        public FanartMovieUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
         {
             _jsonSerializer = jsonSerializer;
             _config = config;
@@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.Movies
                 return;
             }
 
-            var path = FanArtMovieProvider.GetMoviesDataPath(_config.CommonApplicationPaths);
+            var path = FanartMovieProvider.GetMoviesDataPath(_config.CommonApplicationPaths);
 
             Directory.CreateDirectory(path);
             
@@ -118,7 +118,7 @@ namespace MediaBrowser.Providers.Movies
                         return new List<string>();
                     }
 
-                    var updates = _jsonSerializer.DeserializeFromString<List<FanArtUpdatesPrescanTask.FanArtUpdate>>(json);
+                    var updates = _jsonSerializer.DeserializeFromString<List<FanartUpdatesPrescanTask.FanArtUpdate>>(json);
 
                     var existingDictionary = existingIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
 
@@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Movies
             {
                 _logger.Info("Updating movie " + id);
 
-                await FanArtMovieProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false);
+                await FanartMovieProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false);
 
                 numComplete++;
                 double percent = numComplete;

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

@@ -20,7 +20,7 @@ using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.Movies
 {
-    public class ManualFanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor
+    public class ManualFanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
     {
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly IServerConfigurationManager _config;
@@ -86,9 +86,9 @@ namespace MediaBrowser.Providers.Movies
 
             if (!string.IsNullOrEmpty(movieId))
             {
-                await FanArtMovieProvider.Current.EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false);
+                await FanartMovieProvider.Current.EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false);
 
-                var xmlPath = FanArtMovieProvider.Current.GetFanartXmlPath(movieId);
+                var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(movieId);
 
                 try
                 {
@@ -344,7 +344,7 @@ namespace MediaBrowser.Providers.Movies
             if (!string.IsNullOrEmpty(id))
             {
                 // Process images
-                var xmlPath = FanArtMovieProvider.Current.GetFanartXmlPath(id);
+                var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(id);
 
                 var fileInfo = new FileInfo(xmlPath);
 

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

@@ -15,7 +15,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Movies
 {
-    class ManualMovieDbImageProvider : IRemoteImageProvider
+    class ManualMovieDbImageProvider : IRemoteImageProvider, IHasOrder
     {
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IHttpClient _httpClient;

+ 0 - 154
MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs

@@ -1,154 +0,0 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.Music
-{
-    public class AlbumInfoFromSongProvider : BaseMetadataProvider
-    {
-        public AlbumInfoFromSongProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
-            : base(logManager, configurationManager)
-        {
-        }
-
-        public override bool Supports(BaseItem item)
-        {
-            return item is MusicAlbum;
-        }
-
-        protected override bool RefreshOnVersionChange
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        protected override string ProviderVersion
-        {
-            get
-            {
-                return "2";
-            }
-        }
-
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            // If song metadata has changed
-            if (GetComparisonData((MusicAlbum)item) != providerInfo.FileStamp)
-            {
-                return true;
-            }
-
-            return base.NeedsRefreshInternal(item, providerInfo);
-        }
-        /// <summary>
-        /// Gets the data.
-        /// </summary>
-        /// <param name="album">The album.</param>
-        /// <returns>Guid.</returns>
-        private Guid GetComparisonData(MusicAlbum album)
-        {
-            var songs = album.RecursiveChildren.OfType<Audio>().ToList();
-
-            return GetComparisonData(songs);
-        }
-
-        private Guid GetComparisonData(List<Audio> songs)
-        {
-            var albumArtistNames = songs.Select(i => i.AlbumArtist)
-                .Where(i => !string.IsNullOrEmpty(i))
-                .Distinct(StringComparer.OrdinalIgnoreCase)
-                .ToList();
-
-            var studios = songs.SelectMany(i => i.Studios)
-               .Distinct(StringComparer.OrdinalIgnoreCase)
-               .ToList();
-
-            var genres = songs.SelectMany(i => i.Genres)
-               .Distinct(StringComparer.OrdinalIgnoreCase)
-               .ToList();
-
-            albumArtistNames.AddRange(studios);
-            albumArtistNames.AddRange(genres);
-
-            return string.Join(string.Empty, albumArtistNames.OrderBy(i => i).ToArray()).GetMD5();
-        }
-
-        public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            var album = (MusicAlbum)item;
-
-            var songs = album.RecursiveChildren.OfType<Audio>().ToList();
-
-            if (!item.LockedFields.Contains(MetadataFields.Name))
-            {
-                var name = songs.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i));
-
-                if (!string.IsNullOrEmpty(name))
-                {
-                    album.Name = name;
-                }
-            }
-
-            if (!item.LockedFields.Contains(MetadataFields.Studios))
-            {
-                album.Studios = songs.SelectMany(i => i.Studios)
-                    .Distinct(StringComparer.OrdinalIgnoreCase)
-                    .ToList();
-            }
-
-            if (!item.LockedFields.Contains(MetadataFields.Genres))
-            {
-                album.Genres = songs.SelectMany(i => i.Genres)
-                    .Distinct(StringComparer.OrdinalIgnoreCase)
-                    .ToList();
-            } 
-            
-            album.AlbumArtist = songs
-                .Select(i => i.AlbumArtist)
-                .FirstOrDefault(i => !string.IsNullOrEmpty(i));
-
-            album.Artists = songs.SelectMany(i => i.Artists)
-                .Distinct(StringComparer.OrdinalIgnoreCase)
-                .ToList();
-
-            var date = songs.Select(i => i.PremiereDate)
-                .FirstOrDefault(i => i.HasValue);
-
-            if (date.HasValue)
-            {
-                album.PremiereDate = date.Value;
-                album.ProductionYear = date.Value.Year;
-            }
-            else
-            {
-                var year = songs.Select(i => i.ProductionYear ?? 1800).FirstOrDefault(i => i != 1800);
-
-                if (year != 1800)
-                {
-                    album.ProductionYear = year;
-                }
-            }
-
-            providerInfo.FileStamp = GetComparisonData(songs);
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return TrueTaskResult;
-        }
-
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.Second; }
-        }
-    }
-}

+ 188 - 0
MediaBrowser.Providers/Music/AlbumMetadataService.cs

@@ -0,0 +1,188 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Music
+{
+    public class AlbumMetadataService : ConcreteMetadataService<MusicAlbum, AlbumId>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public AlbumMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo)
+        {
+            _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(MusicAlbum source, MusicAlbum target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
+
+        protected override Task SaveItem(MusicAlbum item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+
+        protected override ItemUpdateType AfterMetadataRefresh(MusicAlbum item)
+        {
+            var updateType = base.AfterMetadataRefresh(item);
+
+            var songs = item.RecursiveChildren.OfType<Audio>().ToList(); 
+            
+            if (!item.LockedFields.Contains(MetadataFields.Genres))
+            {
+                var currentList = item.Genres.ToList();
+
+                item.Genres = songs.SelectMany(i => i.Genres)
+                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                    .ToList();
+
+                if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
+                {
+                    updateType = updateType | ItemUpdateType.MetadataDownload;
+                }
+            }
+
+            if (!item.LockedFields.Contains(MetadataFields.Studios))
+            {
+                var currentList = item.Studios.ToList();
+
+                item.Studios = songs.SelectMany(i => i.Studios)
+                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                    .ToList();
+
+                if (currentList.Count != item.Studios.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
+                {
+                    updateType = updateType | ItemUpdateType.MetadataDownload;
+                }
+            }
+
+            if (!item.LockedFields.Contains(MetadataFields.Name))
+            {
+                var name = songs.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i));
+
+                if (!string.IsNullOrEmpty(name))
+                {
+                    if (!string.Equals(item.Name, name, StringComparison.Ordinal))
+                    {
+                        item.Name = name;
+                        updateType = updateType | ItemUpdateType.MetadataDownload;
+                    }
+                }
+            }
+
+            updateType = updateType | SetAlbumArtistFromSongs(item, songs);
+            updateType = updateType | SetArtistsFromSongs(item, songs);
+            updateType = updateType | SetDateFromSongs(item, songs);
+
+            return updateType;
+        }
+
+        protected override AlbumId GetId(MusicAlbum item)
+        {
+            var id = base.GetId(item);
+
+            id.AlbumArtist = item.AlbumArtist;
+
+            var artist = item.Parents.OfType<MusicArtist>().FirstOrDefault();
+
+            if (artist != null)
+            {
+                id.ArtistMusicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz);
+                id.AlbumArtist = id.AlbumArtist ?? artist.Name;
+            }
+
+            return id;
+        }
+
+        private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IEnumerable<Audio> songs)
+        {
+            var updateType = ItemUpdateType.Unspecified;
+            
+            var albumArtist = songs
+                .Select(i => i.AlbumArtist)
+                .FirstOrDefault(i => !string.IsNullOrEmpty(i));
+
+            if (!string.IsNullOrEmpty(albumArtist))
+            {
+                if (!string.Equals(item.AlbumArtist, albumArtist, StringComparison.Ordinal))
+                {
+                    item.AlbumArtist = albumArtist;
+                    updateType = updateType | ItemUpdateType.MetadataDownload;
+                }
+            }
+
+            return updateType;
+        }
+
+        private ItemUpdateType SetArtistsFromSongs(MusicAlbum item, IEnumerable<Audio> songs)
+        {
+            var updateType = ItemUpdateType.Unspecified;
+
+            var currentList = item.Artists.ToList();
+
+            item.Artists = songs.SelectMany(i => i.Artists)
+                .Distinct(StringComparer.OrdinalIgnoreCase)
+                .ToList();
+
+            if (currentList.Count != item.Artists.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Artists.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
+            {
+                updateType = updateType | ItemUpdateType.MetadataDownload;
+            }
+
+            return updateType;
+        }
+
+        private ItemUpdateType SetDateFromSongs(MusicAlbum item, List<Audio> songs)
+        {
+            var updateType = ItemUpdateType.Unspecified;
+
+            var date = songs.Select(i => i.PremiereDate)
+                            .FirstOrDefault(i => i.HasValue);
+
+            var originalPremiereDate = item.PremiereDate;
+            var originalProductionYear = item.ProductionYear;
+
+            if (date.HasValue)
+            {
+                item.PremiereDate = date.Value;
+                item.ProductionYear = date.Value.Year;
+            }
+            else
+            {
+                var year = songs.Select(i => i.ProductionYear ?? 1800).FirstOrDefault(i => i != 1800);
+
+                if (year != 1800)
+                {
+                    item.ProductionYear = year;
+                }
+            }
+
+            if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue) ||
+                (originalProductionYear ?? -1) != (item.ProductionYear ?? -1))
+            {
+                updateType = updateType | ItemUpdateType.MetadataDownload;
+            }
+
+            return updateType;
+        }
+    }
+}

+ 0 - 92
MediaBrowser.Providers/Music/AlbumProviderFromXml.cs

@@ -1,92 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-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.Music
-{
-    class AlbumProviderFromXml : BaseMetadataProvider
-    {
-        private readonly IFileSystem _fileSystem;
-
-        public AlbumProviderFromXml(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 MusicAlbum && item.LocationType == LocationType.FileSystem;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.First; }
-        }
-
-        private const string XmlFileName = "album.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<MusicAlbum>(Logger).Fetch((MusicAlbum)item, path, cancellationToken);
-                }
-                finally
-                {
-                    XmlParsingResourcePool.Release();
-                }
-
-                SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-                return true;
-            }
-
-            return false;
-        }
-    }
-}

+ 59 - 0
MediaBrowser.Providers/Music/AlbumXmlProvider.cs

@@ -0,0 +1,59 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Music
+{
+    class AlbumXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicAlbum>
+    {
+        private readonly ILogger _logger;
+
+        public AlbumXmlProvider(IFileSystem fileSystem, ILogger logger)
+            : base(fileSystem)
+        {
+            _logger = logger;
+        }
+
+        public async Task<MetadataResult<MusicAlbum>> GetMetadata(string path, CancellationToken cancellationToken)
+        {
+            path = GetXmlPath(path);
+
+            var result = new MetadataResult<MusicAlbum>();
+
+            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                var item = new MusicAlbum();
+
+                new BaseItemXmlParser<MusicAlbum>(_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 string GetXmlPath(string path)
+        {
+            return Path.Combine(path, "album.xml");
+        }
+    }
+}

+ 3 - 3
MediaBrowser.Providers/Music/ArtistMetadataService.cs

@@ -13,7 +13,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Music
 {
-    public class ArtistMetadataService : ConcreteMetadataService<MusicArtist>
+    public class ArtistMetadataService : ConcreteMetadataService<MusicArtist, ItemId>
     {
         private readonly ILibraryManager _libraryManager;
 
@@ -49,13 +49,13 @@ namespace MediaBrowser.Providers.Music
             {
                 var songs = item.RecursiveChildren.OfType<Audio>().ToList();
 
-                var currentGenres = item.Genres.ToList();
+                var currentList = item.Genres.ToList();
 
                 item.Genres = songs.SelectMany(i => i.Genres)
                     .Distinct(StringComparer.OrdinalIgnoreCase)
                     .ToList();
 
-                if (currentGenres.Count != item.Genres.Count || !currentGenres.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
+                if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
                 {
                     updateType = updateType | ItemUpdateType.MetadataDownload;
                 }

+ 288 - 125
MediaBrowser.Providers/Music/FanArtAlbumProvider.cs

@@ -3,210 +3,373 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Linq;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Net;
-using System.Net;
+using System.Xml;
 
 namespace MediaBrowser.Providers.Music
 {
-    /// <summary>
-    /// Class FanArtAlbumProvider
-    /// </summary>
-    public class FanArtAlbumProvider : BaseMetadataProvider
+    public class FanartAlbumProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
     {
-        /// <summary>
-        /// The _provider manager
-        /// </summary>
-        private readonly IProviderManager _providerManager;
-
-        /// <summary>
-        /// Gets the HTTP client.
-        /// </summary>
-        /// <value>The HTTP client.</value>
-        protected IHttpClient HttpClient { get; private set; }
-
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+        private readonly IServerConfigurationManager _config;
+        private readonly IHttpClient _httpClient;
         private readonly IFileSystem _fileSystem;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="FanArtAlbumProvider"/> class.
-        /// </summary>
-        /// <param name="httpClient">The HTTP client.</param>
-        /// <param name="logManager">The log manager.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="providerManager">The provider manager.</param>
-        public FanArtAlbumProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
+        public FanartAlbumProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
         {
-            _providerManager = providerManager;
+            _config = config;
+            _httpClient = httpClient;
             _fileSystem = fileSystem;
-            HttpClient = httpClient;
         }
 
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
+        public string Name
         {
-            get { return MetadataProviderPriority.Fifth; }
+            get { return ProviderName; }
         }
 
-        /// <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)
+        public static string ProviderName
+        {
+            get { return "FanArt"; }
+        }
+
+        public bool Supports(IHasImages item)
         {
             return item is MusicAlbum;
         }
 
-        public override ItemUpdateType ItemUpdateType
+        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
         {
-            get
+            return new List<ImageType>
             {
-                return ItemUpdateType.ImageUpdate;
-            }
+                ImageType.Primary, 
+                ImageType.Disc
+            };
         }
-        
-        /// <summary>
-        /// Gets a value indicating whether [refresh on version change].
-        /// </summary>
-        /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
-        protected override bool RefreshOnVersionChange
+
+        public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
         {
-            get
+            var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
+
+            return images.Where(i => i.Type == imageType);
+        }
+
+        public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
+        {
+            var album = (MusicAlbum)item;
+
+            var list = new List<RemoteImageInfo>();
+
+            var artistMusicBrainzId = album.Parent.GetProviderId(MetadataProviders.Musicbrainz);
+
+            if (!string.IsNullOrEmpty(artistMusicBrainzId))
             {
-                return true;
+                await FanartArtistProvider.Current.EnsureMovieXml(artistMusicBrainzId, cancellationToken).ConfigureAwait(false);
+
+                var artistXmlPath = FanartArtistProvider.GetArtistXmlPath(_config.CommonApplicationPaths, artistMusicBrainzId);
+
+                var musicBrainzReleaseGroupId = album.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+
+                var musicBrainzId = album.GetProviderId(MetadataProviders.Musicbrainz);
+
+                try
+                {
+                    AddImages(list, artistXmlPath, musicBrainzId, musicBrainzReleaseGroupId, cancellationToken);
+                }
+                catch (FileNotFoundException)
+                {
+
+                }
             }
+
+            var language = item.GetPreferredMetadataLanguage();
+
+            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
+
+            // Sort first by width to prioritize HD versions
+            return list.OrderByDescending(i => i.Width ?? 0)
+                .ThenByDescending(i =>
+                {
+                    if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return 3;
+                    }
+                    if (!isLanguageEn)
+                    {
+                        if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
+                        {
+                            return 2;
+                        }
+                    }
+                    if (string.IsNullOrEmpty(i.Language))
+                    {
+                        return isLanguageEn ? 3 : 2;
+                    }
+                    return 0;
+                })
+                .ThenByDescending(i => i.CommunityRating ?? 0)
+                .ThenByDescending(i => i.VoteCount ?? 0);
         }
 
         /// <summary>
-        /// Gets the provider version.
+        /// Adds the images.
         /// </summary>
-        /// <value>The provider version.</value>
-        protected override string ProviderVersion
+        /// <param name="list">The list.</param>
+        /// <param name="xmlPath">The XML path.</param>
+        /// <param name="releaseId">The release identifier.</param>
+        /// <param name="releaseGroupId">The release group identifier.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        private void AddImages(List<RemoteImageInfo> list, string xmlPath, string releaseId, string releaseGroupId, CancellationToken cancellationToken)
         {
-            get
+            using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8))
             {
-                return "18";
+                // Use XmlReader for best performance
+                using (var reader = XmlReader.Create(streamReader, new XmlReaderSettings
+                {
+                    CheckCharacters = false,
+                    IgnoreProcessingInstructions = true,
+                    IgnoreComments = true,
+                    ValidationType = ValidationType.None
+                }))
+                {
+                    reader.MoveToContent();
+
+                    // Loop through each element
+                    while (reader.Read())
+                    {
+                        cancellationToken.ThrowIfCancellationRequested();
+
+                        if (reader.NodeType == XmlNodeType.Element)
+                        {
+                            switch (reader.Name)
+                            {
+                                case "music":
+                                    {
+                                        using (var subReader = reader.ReadSubtree())
+                                        {
+                                            AddImagesFromMusicNode(list, releaseId, releaseGroupId, subReader, cancellationToken);
+                                        }
+                                        break;
+                                    }
+
+                                default:
+                                    reader.Skip();
+                                    break;
+                            }
+                        }
+                    }
+                }
             }
         }
 
         /// <summary>
-        /// Needses the refresh internal.
+        /// Adds the images from music node.
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="providerInfo">The provider info.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+        /// <param name="list">The list.</param>
+        /// <param name="releaseId">The release identifier.</param>
+        /// <param name="releaseGroupId">The release group identifier.</param>
+        /// <param name="reader">The reader.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        private void AddImagesFromMusicNode(List<RemoteImageInfo> list, string releaseId, string releaseGroupId, XmlReader reader, CancellationToken cancellationToken)
         {
-            if (!ConfigurationManager.Configuration.DownloadMusicAlbumImages.Disc &&
-                !ConfigurationManager.Configuration.DownloadMusicAlbumImages.Primary)
-            {
-                return false;
-            }
+            reader.MoveToContent();
 
-            if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Disc))
+            while (reader.Read())
             {
-                return false;
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "albums":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    AddImagesFromAlbumsNode(list, releaseId, releaseGroupId, subReader, cancellationToken);
+                                }
+                                break;
+                            }
+                        default:
+                            {
+                                using (reader.ReadSubtree())
+                                {
+                                }
+                                break;
+                            }
+                    }
+                }
             }
-
-            return base.NeedsRefreshInternal(item, providerInfo);
         }
 
-        protected override DateTime CompareDate(BaseItem item)
+        /// <summary>
+        /// Adds the images from albums node.
+        /// </summary>
+        /// <param name="list">The list.</param>
+        /// <param name="releaseId">The release identifier.</param>
+        /// <param name="releaseGroupId">The release group identifier.</param>
+        /// <param name="reader">The reader.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        private void AddImagesFromAlbumsNode(List<RemoteImageInfo> list, string releaseId, string releaseGroupId, XmlReader reader, CancellationToken cancellationToken)
         {
-            var artistMusicBrainzId = item.Parent.GetProviderId(MetadataProviders.Musicbrainz);
+            reader.MoveToContent();
 
-            if (!string.IsNullOrEmpty(artistMusicBrainzId))
+            while (reader.Read())
             {
-                var artistXmlPath = FanartArtistProvider.GetArtistXmlPath(ConfigurationManager.CommonApplicationPaths, artistMusicBrainzId);
-
-                var file = new FileInfo(artistXmlPath);
-
-                if (file.Exists)
+                if (reader.NodeType == XmlNodeType.Element)
                 {
-                    return _fileSystem.GetLastWriteTimeUtc(file);
+                    switch (reader.Name)
+                    {
+                        case "album":
+                            {
+                                var id = reader.GetAttribute("id");
+
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    if (string.Equals(id, releaseId, StringComparison.OrdinalIgnoreCase) ||
+                                        string.Equals(id, releaseGroupId, StringComparison.OrdinalIgnoreCase))
+                                    {
+                                        AddImages(list, subReader, cancellationToken);
+                                    }
+                                }
+                                break;
+                            }
+                        default:
+                            {
+                                using (reader.ReadSubtree())
+                                {
+                                }
+                                break;
+                            }
+                    }
                 }
-            } 
-            
-            return base.CompareDate(item);
+            }
         }
 
         /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
+        /// Adds the images.
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
+        /// <param name="list">The list.</param>
+        /// <param name="reader">The reader.</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)
+        private void AddImages(List<RemoteImageInfo> list, XmlReader reader, CancellationToken cancellationToken)
         {
-            if (!item.LockedFields.Contains(MetadataFields.Images))
+            reader.MoveToContent();
+
+            while (reader.Read())
             {
-                var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartAlbumProvider.ProviderName).ConfigureAwait(false);
-                await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false);
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "cdart":
+                            {
+                                AddImage(list, reader, ImageType.Disc, 1000, 1000);
+                                break;
+                            }
+                        case "albumcover":
+                            {
+                                AddImage(list, reader, ImageType.Primary, 1000, 1000);
+                                break;
+                            }
+                        default:
+                            {
+                                using (reader.ReadSubtree())
+                                {
+                                }
+                                break;
+                            }
+                    }
+                }
             }
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-
-            return true;
         }
 
         /// <summary>
-        /// Fetches from XML.
+        /// Adds the image.
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="images">The images.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        private async Task FetchFromXml(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
+        /// <param name="list">The list.</param>
+        /// <param name="reader">The reader.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="width">The width.</param>
+        /// <param name="height">The height.</param>
+        private void AddImage(List<RemoteImageInfo> list, XmlReader reader, ImageType type, int width, int height)
         {
-            cancellationToken.ThrowIfCancellationRequested();
+            var url = reader.GetAttribute("url");
+
+            var size = reader.GetAttribute("size");
 
-            if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Primary && !item.HasImage(ImageType.Primary))
+            if (!string.IsNullOrEmpty(size))
             {
-                await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
+                int sizeNum;
+                if (int.TryParse(size, NumberStyles.Any, _usCulture, out sizeNum))
+                {
+                    width = sizeNum;
+                    height = sizeNum;
+                }
             }
 
-            cancellationToken.ThrowIfCancellationRequested();
+            var likesString = reader.GetAttribute("likes");
+            int likes;
 
-            if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Disc && !item.HasImage(ImageType.Disc))
+            var info = new RemoteImageInfo
             {
-                await SaveImage(item, images, ImageType.Disc, cancellationToken).ConfigureAwait(false);
+                RatingType = RatingType.Likes,
+                Type = type,
+                Width = width,
+                Height = height,
+                ProviderName = Name,
+                Url = url,
+                Language = reader.GetAttribute("lang")
+            };
+
+            if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes))
+            {
+                info.CommunityRating = likes;
             }
+
+            list.Add(info);
         }
 
-        private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
+        public int Order
         {
-            foreach (var image in images.Where(i => i.Type == type))
+            get { return 0; }
+        }
+
+        public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
+        {
+            return _httpClient.GetResponse(new HttpRequestOptions
             {
-                try
-                {
-                    await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
-                    break;
-                }
-                catch (HttpException ex)
-                {
-                    // Sometimes fanart has bad url's in their xml
-                    if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
-                    {
-                        continue;
-                    }
-                    break;
-                }
+                CancellationToken = cancellationToken,
+                Url = url,
+                ResourcePool = FanartArtistProvider.FanArtResourcePool
+            });
+        }
+
+        public bool HasChanged(IHasMetadata item, DateTime date)
+        {
+            var album = (MusicAlbum)item;
+
+            var artistMusicBrainzId = album.Parent.GetProviderId(MetadataProviders.Musicbrainz);
+
+            if (!String.IsNullOrEmpty(artistMusicBrainzId))
+            {
+                // Process images
+                var artistXmlPath = FanartArtistProvider.GetArtistXmlPath(_config.CommonApplicationPaths, artistMusicBrainzId);
+
+                var fileInfo = new FileInfo(artistXmlPath);
+
+                return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date;
             }
+
+            return false;
         }
     }
 }

+ 1 - 1
MediaBrowser.Providers/Music/FanArtArtistProvider.cs

@@ -20,7 +20,7 @@ using System.Xml;
 
 namespace MediaBrowser.Providers.Music
 {
-    public class FanartArtistProvider : IRemoteImageProvider, IHasChangeMonitor
+    public class FanartArtistProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
     {
         internal static readonly SemaphoreSlim FanArtResourcePool = new SemaphoreSlim(3, 3);
         internal const string ApiKey = "5c6b04c68e904cfed1e6cbc9a9e683d4";

+ 2 - 2
MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs

@@ -15,7 +15,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Music
 {
-    class FanArtUpdatesPrescanTask : ILibraryPostScanTask
+    class FanartUpdatesPrescanTask : ILibraryPostScanTask
     {
         private const string UpdatesUrl = "http://api.fanart.tv/webservice/newmusic/{0}/{1}/";
 
@@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Music
 
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
-        public FanArtUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
+        public FanartUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
         {
             _jsonSerializer = jsonSerializer;
             _config = config;

+ 120 - 68
MediaBrowser.Providers/Music/LastFmImageProvider.cs

@@ -1,113 +1,165 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Music
 {
-    /// <summary>
-    /// Class LastFmArtistImageProvider
-    /// </summary>
-    public class LastFmImageProvider : BaseMetadataProvider
+    public class LastfmImageProvider : IRemoteImageProvider, IHasOrder
     {
-        /// <summary>
-        /// The _provider manager
-        /// </summary>
-        private readonly IProviderManager _providerManager;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="LastFmImageProvider"/> class.
-        /// </summary>
-        /// <param name="logManager">The log manager.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="providerManager">The provider manager.</param>
-        public LastFmImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : 
-            base(logManager, configurationManager)
+        private readonly IHttpClient _httpClient;
+        private readonly IServerConfigurationManager _config;
+
+        public LastfmImageProvider(IHttpClient httpClient, IServerConfigurationManager config)
+        {
+            _httpClient = httpClient;
+            _config = config;
+        }
+
+        public string Name
+        {
+            get { return ProviderName; }
+        }
+
+        public static string ProviderName
         {
-            _providerManager = providerManager;
+            get { return "last.fm"; }
         }
 
-        /// <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)
+        public bool Supports(IHasImages item)
         {
-            return item is MusicAlbum;
+            return item is MusicAlbum || item is MusicArtist;
         }
 
-        /// <summary>
-        /// Needses the refresh internal.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="providerInfo">The provider info.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
         {
-            if (item.HasImage(ImageType.Primary))
+            return new List<ImageType>
             {
-                return false;
-            }
+                ImageType.Primary
+            };
+        }
+
+        public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
+        {
+            var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
 
-            return base.NeedsRefreshInternal(item, providerInfo);
+            return images.Where(i => i.Type == imageType);
         }
 
-        /// <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)
+        public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
         {
-            if (!item.HasImage(ImageType.Primary))
+            var list = new List<RemoteImageInfo>();
+
+            RemoteImageInfo info = null;
+
+            var musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz);
+
+            if (!string.IsNullOrEmpty(musicBrainzId))
             {
-                var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualLastFmImageProvider.ProviderName).ConfigureAwait(false);
+                var cachePath = Path.Combine(_config.ApplicationPaths.CachePath, "lastfm", musicBrainzId, "image.txt");
+
+                try
+                {
+                    var parts = File.ReadAllText(cachePath).Split('|');
 
-                await DownloadImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
+                    info = GetInfo(parts.FirstOrDefault(), parts.LastOrDefault());
+                }
+                catch (DirectoryNotFoundException)
+                {
+                }
+                catch (FileNotFoundException)
+                {
+                }
             }
 
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+            if (info ==  null)
+            {
+                var musicBrainzReleaseGroupId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+
+                if (!string.IsNullOrEmpty(musicBrainzReleaseGroupId))
+                {
+                    var cachePath = Path.Combine(_config.ApplicationPaths.CachePath, "lastfm", musicBrainzReleaseGroupId, "image.txt");
+
+                    try
+                    {
+                        var parts = File.ReadAllText(cachePath).Split('|');
 
-            return true;
+                        info = GetInfo(parts.FirstOrDefault(), parts.LastOrDefault());
+                    }
+                    catch (DirectoryNotFoundException)
+                    {
+                    }
+                    catch (FileNotFoundException)
+                    {
+                    }
+                }
+            }
+
+            if (info != null)
+            {
+                list.Add(info);
+            }
+
+            // The only info we have is size
+            return Task.FromResult<IEnumerable<RemoteImageInfo>>(list.OrderByDescending(i => i.Width ?? 0));
         }
 
-        private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
+        private RemoteImageInfo GetInfo(string url, string size)
         {
-            cancellationToken.ThrowIfCancellationRequested();
+            if (string.IsNullOrEmpty(url))
+            {
+                return null;
+            }
 
-            var configSetting = item is MusicAlbum
-                ? ConfigurationManager.Configuration.DownloadMusicAlbumImages
-                : ConfigurationManager.Configuration.DownloadMusicArtistImages;
+            var info = new RemoteImageInfo
+            {
+                ProviderName = Name,
+                Url = url,
+                Type = ImageType.Primary
+            };
 
-            if (configSetting.Primary && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
+            if (string.Equals(size, "mega", StringComparison.OrdinalIgnoreCase))
+            {
+                
+            }
+            else if (string.Equals(size, "extralarge", StringComparison.OrdinalIgnoreCase))
+            {
+
+            }
+            else if (string.Equals(size, "large", StringComparison.OrdinalIgnoreCase))
+            {
+
+            }
+            else if (string.Equals(size, "medium", StringComparison.OrdinalIgnoreCase))
             {
-                var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
 
-                if (image != null)
-                {
-                    await _providerManager.SaveImage(item, image.Url, LastFmArtistProvider.LastfmResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
-                }
             }
+
+            return info;
+        }
+
+        public int Order
+        {
+            get { return 1; }
         }
 
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
+        public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            get { return MetadataProviderPriority.Fifth; }
+            return _httpClient.GetResponse(new HttpRequestOptions
+            {
+                CancellationToken = cancellationToken,
+                Url = url,
+                ResourcePool = LastfmArtistProvider.LastfmResourcePool
+            });
         }
     }
 }

+ 195 - 114
MediaBrowser.Providers/Music/LastfmAlbumProvider.cs

@@ -1,106 +1,51 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
-using MoreLinq;
 using System;
+using System.Collections.Generic;
 using System.IO;
-using System.Linq;
+using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Music
 {
-    public class LastfmAlbumProvider : LastfmBaseProvider
+    public class LastfmAlbumProvider : IRemoteMetadataProvider<MusicAlbum>, IHasOrder
     {
-        internal static LastfmAlbumProvider Current;
+        private readonly IJsonSerializer _json;
+        private readonly IHttpClient _httpClient;
 
-        public LastfmAlbumProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager)
-            : base(jsonSerializer, httpClient, logManager, configurationManager)
-        {
-            Current = this;
-        }
+        private readonly IServerConfigurationManager _config;
+        private readonly ILogger _logger;
 
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
+        public LastfmAlbumProvider(IHttpClient httpClient, IJsonSerializer json, IServerConfigurationManager config, ILogger logger)
         {
-            get { return MetadataProviderPriority.Fourth; }
+            _httpClient = httpClient;
+            _json = json;
+            _config = config;
+            _logger = logger;
         }
 
-        protected override string ProviderVersion
+        public async Task<MetadataResult<MusicAlbum>> GetMetadata(ItemId id, CancellationToken cancellationToken)
         {
-            get
-            {
-                return "9";
-            }
-        }
+            var result = new MetadataResult<MusicAlbum>();
 
-        private bool HasAltMeta(BaseItem item)
-        {
-            return item.LocationType == LocationType.FileSystem && item.ResolveArgs.ContainsMetaFileByName("album.xml");
-        }
-        
-        /// <summary>
-        /// Needses the refresh internal.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="providerInfo">The provider info.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var hasId = !string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Musicbrainz)) &&
-                        !string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup));
+            var lastFmData = await GetAlbumResult((AlbumId)id, cancellationToken).ConfigureAwait(false);
 
-            if (hasId && HasAltMeta(item))
+            if (lastFmData != null && lastFmData.album != null)
             {
-                return false;
+                result.HasMetadata = true;
+                ProcessAlbumData(result.Item, lastFmData.album);
             }
 
-            // If song metadata has changed and we don't have an mbid, refresh
-            if (!hasId && GetComparisonData(item as MusicAlbum) != providerInfo.FileStamp)
-            {
-                return true;
-            }
-
-            return base.NeedsRefreshInternal(item, providerInfo);
+            return result;
         }
 
-        /// <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 album = (MusicAlbum)item;
-
-            var result = await GetAlbumResult(album, cancellationToken).ConfigureAwait(false);
-
-            if (result != null && result.album != null)
-            {
-                LastfmHelper.ProcessAlbumData(item, result.album);
-            }
-
-            providerInfo.FileStamp = GetComparisonData(album);
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
-        }
-
-
-        private async Task<LastfmGetAlbumResult> GetAlbumResult(MusicAlbum item, CancellationToken cancellationToken)
+        private async Task<LastfmGetAlbumResult> GetAlbumResult(AlbumId item, CancellationToken cancellationToken)
         {
             // Try album release Id
             if (!string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Musicbrainz)))
@@ -123,33 +68,37 @@ namespace MediaBrowser.Providers.Music
                     return result;
                 }
             }
-            
-            // Get each song, distinct by the combination of AlbumArtist and Album
-            var songs = item.RecursiveChildren.OfType<Audio>().DistinctBy(i => (i.AlbumArtist ?? string.Empty) + (i.Album ?? string.Empty), StringComparer.OrdinalIgnoreCase).ToList();
 
-            foreach (var song in songs.Where(song => !string.IsNullOrEmpty(song.Album) && !string.IsNullOrEmpty(song.AlbumArtist)))
-            {
-                var result = await GetAlbumResult(song.AlbumArtist, song.Album, cancellationToken).ConfigureAwait(false);
+            //// Get each song, distinct by the combination of AlbumArtist and Album
+            //var songs = item.RecursiveChildren.OfType<Audio>().DistinctBy(i => (i.AlbumArtist ?? string.Empty) + (i.Album ?? string.Empty), StringComparer.OrdinalIgnoreCase).ToList();
 
-                if (result != null && result.album != null)
-                {
-                    return result;
-                }
+            //foreach (var song in songs.Where(song => !string.IsNullOrEmpty(song.Album) && !string.IsNullOrEmpty(song.AlbumArtist)))
+            //{
+            //    var result = await GetAlbumResult(song.AlbumArtist, song.Album, cancellationToken).ConfigureAwait(false);
+
+            //    if (result != null && result.album != null)
+            //    {
+            //        return result;
+            //    }
+            //}
+
+            if (string.IsNullOrEmpty(item.AlbumArtist))
+            {
+                return null;
             }
 
-            // Try the folder name
-            return await GetAlbumResult(item.Parent.Name, item.Name, cancellationToken);
+            return await GetAlbumResult(item.AlbumArtist, item.Name, cancellationToken);
         }
 
         private async Task<LastfmGetAlbumResult> GetAlbumResult(string artist, string album, CancellationToken cancellationToken)
         {
             // Get albu info using artist and album name
-            var url = LastFmArtistProvider.RootUrl + string.Format("method=album.getInfo&artist={0}&album={1}&api_key={2}&format=json", UrlEncode(artist), UrlEncode(album), LastFmArtistProvider.ApiKey);
+            var url = LastfmArtistProvider.RootUrl + string.Format("method=album.getInfo&artist={0}&album={1}&api_key={2}&format=json", UrlEncode(artist), UrlEncode(album), LastfmArtistProvider.ApiKey);
 
-            using (var json = await HttpClient.Get(new HttpRequestOptions
+            using (var json = await _httpClient.Get(new HttpRequestOptions
             {
                 Url = url,
-                ResourcePool = LastFmArtistProvider.LastfmResourcePool,
+                ResourcePool = LastfmArtistProvider.LastfmResourcePool,
                 CancellationToken = cancellationToken,
                 EnableHttpCompression = false
 
@@ -162,7 +111,7 @@ namespace MediaBrowser.Providers.Music
                     // Fix their bad json
                     jsonText = jsonText.Replace("\"#text\"", "\"url\"");
 
-                    return JsonSerializer.DeserializeFromString<LastfmGetAlbumResult>(jsonText);
+                    return _json.DeserializeFromString<LastfmGetAlbumResult>(jsonText);
                 }
             }
         }
@@ -170,48 +119,180 @@ namespace MediaBrowser.Providers.Music
         private async Task<LastfmGetAlbumResult> GetAlbumResult(string musicbraizId, CancellationToken cancellationToken)
         {
             // Get albu info using artist and album name
-            var url = LastFmArtistProvider.RootUrl + string.Format("method=album.getInfo&mbid={0}&api_key={1}&format=json", musicbraizId, LastFmArtistProvider.ApiKey);
+            var url = LastfmArtistProvider.RootUrl + string.Format("method=album.getInfo&mbid={0}&api_key={1}&format=json", musicbraizId, LastfmArtistProvider.ApiKey);
 
-            using (var json = await HttpClient.Get(new HttpRequestOptions
+            using (var json = await _httpClient.Get(new HttpRequestOptions
             {
                 Url = url,
-                ResourcePool = LastFmArtistProvider.LastfmResourcePool,
+                ResourcePool = LastfmArtistProvider.LastfmResourcePool,
                 CancellationToken = cancellationToken,
                 EnableHttpCompression = false
 
             }).ConfigureAwait(false))
             {
-                return JsonSerializer.DeserializeFromStream<LastfmGetAlbumResult>(json);
+                return _json.DeserializeFromStream<LastfmGetAlbumResult>(json);
             }
         }
 
-        public override bool Supports(BaseItem item)
+        private void ProcessAlbumData(MusicAlbum item, LastfmAlbum data)
         {
-            return item is MusicAlbum;
+            var overview = data.wiki != null ? data.wiki.content : null;
+
+            if (!item.LockedFields.Contains(MetadataFields.Overview))
+            {
+                item.Overview = overview;
+            }
+
+            // Only grab the date here if the album doesn't already have one, since id3 tags are preferred
+            DateTime release;
+
+            if (DateTime.TryParse(data.releasedate, out release))
+            {
+                // Lastfm sends back null as sometimes 1901, other times 0
+                if (release.Year > 1901)
+                {
+                    if (!item.PremiereDate.HasValue)
+                    {
+                        item.PremiereDate = release;
+                    }
+
+                    if (!item.ProductionYear.HasValue)
+                    {
+                        item.ProductionYear = release.Year;
+                    }
+                }
+            }
+
+            string imageSize;
+            var url = LastfmHelper.GetImageUrl(data, out imageSize);
+
+            var musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz) ??
+                item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+
+            LastfmHelper.SaveImageInfo(_config.ApplicationPaths, _logger, musicBrainzId, url, imageSize);
         }
 
         /// <summary>
-        /// Gets the data.
+        /// Encodes an URL.
         /// </summary>
-        /// <param name="album">The album.</param>
-        /// <returns>Guid.</returns>
-        private Guid GetComparisonData(MusicAlbum album)
+        /// <param name="name">The name.</param>
+        /// <returns>System.String.</returns>
+        private string UrlEncode(string name)
+        {
+            return WebUtility.UrlEncode(name);
+        }
+
+        public string Name
         {
-            var songs = album.RecursiveChildren.OfType<Audio>().ToList();
+            get { return "last.fm"; }
+        }
 
-            var albumArtists = songs.Select(i => i.AlbumArtist)
-                .Where(i => !string.IsNullOrEmpty(i))
-                .Distinct(StringComparer.OrdinalIgnoreCase)
-                .ToList();
+        public int Order
+        {
+            get { return 1; }
+        }
+    }
 
-            var albumNames = songs.Select(i => i.AlbumArtist)
-                .Where(i => !string.IsNullOrEmpty(i))
-                .Distinct(StringComparer.OrdinalIgnoreCase)
-                .ToList();
+    #region Result Objects
 
-            albumArtists.AddRange(albumNames);
+    public class LastfmStats
+    {
+        public string listeners { get; set; }
+        public string playcount { get; set; }
+    }
 
-            return string.Join(string.Empty, albumArtists.OrderBy(i => i).ToArray()).GetMD5();
-        }
+    public class LastfmTag
+    {
+        public string name { get; set; }
+        public string url { get; set; }
     }
+
+
+    public class LastfmTags
+    {
+        public List<LastfmTag> tag { get; set; }
+    }
+
+    public class LastfmFormationInfo
+    {
+        public string yearfrom { get; set; }
+        public string yearto { get; set; }
+    }
+
+    public class LastFmBio
+    {
+        public string published { get; set; }
+        public string summary { get; set; }
+        public string content { get; set; }
+        public string placeformed { get; set; }
+        public string yearformed { get; set; }
+        public List<LastfmFormationInfo> formationlist { get; set; }
+    }
+
+    public class LastFmImage
+    {
+        public string url { get; set; }
+        public string size { get; set; }
+    }
+
+    public class LastfmArtist : IHasLastFmImages
+    {
+        public string name { get; set; }
+        public string mbid { get; set; }
+        public string url { get; set; }
+        public string streamable { get; set; }
+        public string ontour { get; set; }
+        public LastfmStats stats { get; set; }
+        public List<LastfmArtist> similar { get; set; }
+        public LastfmTags tags { get; set; }
+        public LastFmBio bio { get; set; }
+        public List<LastFmImage> image { get; set; }
+    }
+
+
+    public class LastfmAlbum : IHasLastFmImages
+    {
+        public string name { get; set; }
+        public string artist { get; set; }
+        public string id { get; set; }
+        public string mbid { get; set; }
+        public string releasedate { get; set; }
+        public int listeners { get; set; }
+        public int playcount { get; set; }
+        public LastfmTags toptags { get; set; }
+        public LastFmBio wiki { get; set; }
+        public List<LastFmImage> image { get; set; }
+    }
+
+    public interface IHasLastFmImages
+    {
+        List<LastFmImage> image { get; set; }
+    }
+
+    public class LastfmGetAlbumResult
+    {
+        public LastfmAlbum album { get; set; }
+    }
+
+    public class LastfmGetArtistResult
+    {
+        public LastfmArtist artist { get; set; }
+    }
+
+    public class Artistmatches
+    {
+        public List<LastfmArtist> artist { get; set; }
+    }
+
+    public class LastfmArtistSearchResult
+    {
+        public Artistmatches artistmatches { get; set; }
+    }
+
+    public class LastfmArtistSearchResults
+    {
+        public LastfmArtistSearchResult results { get; set; }
+    }
+
+    #endregion
 }

+ 6 - 22
MediaBrowser.Providers/Music/LastfmArtistProvider.cs

@@ -19,7 +19,7 @@ using System.Xml;
 
 namespace MediaBrowser.Providers.Music
 {
-    public class LastFmArtistProvider : IRemoteMetadataProvider<MusicArtist>
+    public class LastfmArtistProvider : IRemoteMetadataProvider<MusicArtist>
     {
         private readonly IJsonSerializer _json;
         private readonly IHttpClient _httpClient;
@@ -30,12 +30,14 @@ namespace MediaBrowser.Providers.Music
         internal static string ApiKey = "7b76553c3eb1d341d642755aecc40a33";
 
         private readonly IServerConfigurationManager _config;
-        private ILogger _logger;
+        private readonly ILogger _logger;
 
-        public LastFmArtistProvider(IHttpClient httpClient, IJsonSerializer json)
+        public LastfmArtistProvider(IHttpClient httpClient, IJsonSerializer json, IServerConfigurationManager config, ILogger logger)
         {
             _httpClient = httpClient;
             _json = json;
+            _config = config;
+            _logger = logger;
         }
 
         public async Task<MetadataResult<MusicArtist>> GetMetadata(ItemId id, CancellationToken cancellationToken)
@@ -119,25 +121,7 @@ namespace MediaBrowser.Providers.Music
             string imageSize;
             var url = LastfmHelper.GetImageUrl(data, out imageSize);
 
-            var cachePath = Path.Combine(_config.ApplicationPaths.CachePath, "lastfm", musicBrainzId, "image.txt");
-
-            try
-            {
-                if (string.IsNullOrEmpty(url))
-                {
-                    File.Delete(cachePath);
-                }
-                else
-                {
-                    Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
-                    File.WriteAllText(cachePath, url + "|" + imageSize);
-                }
-            }
-            catch (IOException ex)
-            {
-                // Don't fail if this is unable to write
-                _logger.ErrorException("Error saving to {0}", ex, cachePath);
-            }
+            LastfmHelper.SaveImageInfo(_config.ApplicationPaths, _logger, musicBrainzId, url, imageSize);
         }
         
         private async Task<string> FindId(ItemId item, CancellationToken cancellationToken)

+ 0 - 194
MediaBrowser.Providers/Music/LastfmBaseProvider.cs

@@ -1,194 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Threading;
-
-namespace MediaBrowser.Providers.Music
-{
-    /// <summary>
-    /// Class MovieDbProvider
-    /// </summary>
-    public abstract class LastfmBaseProvider : BaseMetadataProvider
-    {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="LastfmBaseProvider" /> class.
-        /// </summary>
-        /// <param name="jsonSerializer">The json serializer.</param>
-        /// <param name="httpClient">The HTTP client.</param>
-        /// <param name="logManager">The log manager.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
-        protected LastfmBaseProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager)
-            : base(logManager, configurationManager)
-        {
-            if (jsonSerializer == null)
-            {
-                throw new ArgumentNullException("jsonSerializer");
-            }
-            if (httpClient == null)
-            {
-                throw new ArgumentNullException("httpClient");
-            }
-            JsonSerializer = jsonSerializer;
-            HttpClient = httpClient;
-        }
-
-        protected override string ProviderVersion
-        {
-            get
-            {
-                return "8";
-            }
-        }
-
-        protected override bool RefreshOnVersionChange
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Gets the json serializer.
-        /// </summary>
-        /// <value>The json serializer.</value>
-        protected IJsonSerializer JsonSerializer { get; private set; }
-
-        /// <summary>
-        /// Gets the HTTP client.
-        /// </summary>
-        /// <value>The HTTP client.</value>
-        protected IHttpClient HttpClient { get; private set; }
-
-        /// <summary>
-        /// Gets a value indicating whether [requires internet].
-        /// </summary>
-        /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
-        public override bool RequiresInternet
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Encodes an URL.
-        /// </summary>
-        /// <param name="name">The name.</param>
-        /// <returns>System.String.</returns>
-        protected static string UrlEncode(string name)
-        {
-            return WebUtility.UrlEncode(name);
-        }
-    }
-
-    #region Result Objects
-
-    public class LastfmStats
-    {
-        public string listeners { get; set; }
-        public string playcount { get; set; }
-    }
-
-    public class LastfmTag
-    {
-        public string name { get; set; }
-        public string url { get; set; }
-    }
-
-
-    public class LastfmTags
-    {
-        public List<LastfmTag> tag { get; set; }
-    }
-
-    public class LastfmFormationInfo
-    {
-        public string yearfrom { get; set; }
-        public string yearto { get; set; }
-    }
-
-    public class LastFmBio
-    {
-        public string published { get; set; }
-        public string summary { get; set; }
-        public string content { get; set; }
-        public string placeformed { get; set; }
-        public string yearformed { get; set; }
-        public List<LastfmFormationInfo> formationlist { get; set; }
-    }
-
-    public class LastFmImage
-    {
-        public string url { get; set; }
-        public string size { get; set; }
-    }
-
-    public class LastfmArtist : IHasLastFmImages
-    {
-        public string name { get; set; }
-        public string mbid { get; set; }
-        public string url { get; set; }
-        public string streamable { get; set; }
-        public string ontour { get; set; }
-        public LastfmStats stats { get; set; }
-        public List<LastfmArtist> similar { get; set; }
-        public LastfmTags tags { get; set; }
-        public LastFmBio bio { get; set; }
-        public List<LastFmImage> image { get; set; }
-    }
-
-
-    public class LastfmAlbum : IHasLastFmImages
-    {
-        public string name { get; set; }
-        public string artist { get; set; }
-        public string id { get; set; }
-        public string mbid { get; set; }
-        public string releasedate { get; set; }
-        public int listeners { get; set; }
-        public int playcount { get; set; }
-        public LastfmTags toptags { get; set; }
-        public LastFmBio wiki { get; set; }
-        public List<LastFmImage> image { get; set; }
-    }
-
-    public interface IHasLastFmImages
-    {
-        List<LastFmImage> image { get; set; }
-    }
-
-    public class LastfmGetAlbumResult
-    {
-        public LastfmAlbum album { get; set; }
-    }
-
-    public class LastfmGetArtistResult
-    {
-        public LastfmArtist artist { get; set; }
-    }
-
-    public class Artistmatches
-    {
-        public List<LastfmArtist> artist { get; set; }
-    }
-
-    public class LastfmArtistSearchResult
-    {
-        public Artistmatches artistmatches { get; set; }
-    }
-
-    public class LastfmArtistSearchResults
-    {
-        public LastfmArtistSearchResult results { get; set; }
-    }
-
-    #endregion
-}

+ 18 - 32
MediaBrowser.Providers/Music/LastfmHelper.cs

@@ -1,7 +1,7 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Logging;
 using System;
+using System.IO;
 using System.Linq;
 
 namespace MediaBrowser.Providers.Music
@@ -37,41 +37,27 @@ namespace MediaBrowser.Providers.Music
             return null;
         }
 
-        public static void ProcessAlbumData(BaseItem item, LastfmAlbum data)
+        public static void SaveImageInfo(IApplicationPaths appPaths, ILogger logger, string musicBrainzId, string url, string size)
         {
-            var overview = data.wiki != null ? data.wiki.content : null;
+            var cachePath = Path.Combine(appPaths.CachePath, "lastfm", musicBrainzId, "image.txt");
 
-            if (!item.LockedFields.Contains(MetadataFields.Overview))
+            try
             {
-                item.Overview = overview;
-            }
-
-            // Only grab the date here if the album doesn't already have one, since id3 tags are preferred
-            DateTime release;
-
-            if (DateTime.TryParse(data.releasedate, out release))
-            {
-                // Lastfm sends back null as sometimes 1901, other times 0
-                if (release.Year > 1901)
+                if (string.IsNullOrEmpty(url))
                 {
-                    if (!item.PremiereDate.HasValue)
-                    {
-                        item.PremiereDate = release;
-                    }
-
-                    if (!item.ProductionYear.HasValue)
-                    {
-                        item.ProductionYear = release.Year;
-                    }
+                    File.Delete(cachePath);
+                }
+                else
+                {
+                    Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
+                    File.WriteAllText(cachePath, url + "|" + size);
                 }
             }
-
-            var album = (MusicAlbum)item;
-
-            string imageSize;
-
-            album.LastFmImageUrl = GetImageUrl(data, out imageSize);
-            album.LastFmImageSize = imageSize;
+            catch (IOException ex)
+            {
+                // Don't fail if this is unable to write
+                logger.ErrorException("Error saving to {0}", ex, cachePath);
+            }
         }
     }
 }

+ 0 - 354
MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs

@@ -1,354 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml;
-
-namespace MediaBrowser.Providers.Music
-{
-    public class ManualFanartAlbumProvider : IRemoteImageProvider
-    {
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-        private readonly IServerConfigurationManager _config;
-        private readonly IHttpClient _httpClient;
-
-        public ManualFanartAlbumProvider(IServerConfigurationManager config, IHttpClient httpClient)
-        {
-            _config = config;
-            _httpClient = httpClient;
-        }
-
-        public string Name
-        {
-            get { return ProviderName; }
-        }
-
-        public static string ProviderName
-        {
-            get { return "FanArt"; }
-        }
-
-        public bool Supports(IHasImages item)
-        {
-            return item is MusicAlbum;
-        }
-
-        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
-        {
-            return new List<ImageType>
-            {
-                ImageType.Primary, 
-                ImageType.Disc
-            };
-        }
-
-        public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
-        {
-            var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
-
-            return images.Where(i => i.Type == imageType);
-        }
-
-        public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
-        {
-            var album = (MusicAlbum)item;
-
-            var list = new List<RemoteImageInfo>();
-
-            var artistMusicBrainzId = album.Parent.GetProviderId(MetadataProviders.Musicbrainz);
-
-            if (!string.IsNullOrEmpty(artistMusicBrainzId))
-            {
-                var artistXmlPath = FanartArtistProvider.GetArtistXmlPath(_config.CommonApplicationPaths, artistMusicBrainzId);
-
-                var musicBrainzReleaseGroupId = album.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
-
-                var musicBrainzId = album.GetProviderId(MetadataProviders.Musicbrainz);
-
-                try
-                {
-                    AddImages(list, artistXmlPath, musicBrainzId, musicBrainzReleaseGroupId, cancellationToken);
-                }
-                catch (FileNotFoundException)
-                {
-
-                }
-            }
-
-            var language = item.GetPreferredMetadataLanguage();
-
-            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
-            // Sort first by width to prioritize HD versions
-            list = list.OrderByDescending(i => i.Width ?? 0)
-                .ThenByDescending(i =>
-                {
-                    if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
-                    {
-                        return 3;
-                    }
-                    if (!isLanguageEn)
-                    {
-                        if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
-                        {
-                            return 2;
-                        }
-                    }
-                    if (string.IsNullOrEmpty(i.Language))
-                    {
-                        return isLanguageEn ? 3 : 2;
-                    }
-                    return 0;
-                })
-                .ThenByDescending(i => i.CommunityRating ?? 0)
-                .ThenByDescending(i => i.VoteCount ?? 0)
-                .ToList();
-
-            return Task.FromResult<IEnumerable<RemoteImageInfo>>(list);
-        }
-
-        /// <summary>
-        /// Adds the images.
-        /// </summary>
-        /// <param name="list">The list.</param>
-        /// <param name="xmlPath">The XML path.</param>
-        /// <param name="releaseId">The release identifier.</param>
-        /// <param name="releaseGroupId">The release group identifier.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        private void AddImages(List<RemoteImageInfo> list, string xmlPath, string releaseId, string releaseGroupId, CancellationToken cancellationToken)
-        {
-            using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8))
-            {
-                // Use XmlReader for best performance
-                using (var reader = XmlReader.Create(streamReader, new XmlReaderSettings
-                {
-                    CheckCharacters = false,
-                    IgnoreProcessingInstructions = true,
-                    IgnoreComments = true,
-                    ValidationType = ValidationType.None
-                }))
-                {
-                    reader.MoveToContent();
-
-                    // Loop through each element
-                    while (reader.Read())
-                    {
-                        cancellationToken.ThrowIfCancellationRequested();
-
-                        if (reader.NodeType == XmlNodeType.Element)
-                        {
-                            switch (reader.Name)
-                            {
-                                case "music":
-                                    {
-                                        using (var subReader = reader.ReadSubtree())
-                                        {
-                                            AddImagesFromMusicNode(list, releaseId, releaseGroupId, subReader, cancellationToken);
-                                        }
-                                        break;
-                                    }
-
-                                default:
-                                    reader.Skip();
-                                    break;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Adds the images from music node.
-        /// </summary>
-        /// <param name="list">The list.</param>
-        /// <param name="releaseId">The release identifier.</param>
-        /// <param name="releaseGroupId">The release group identifier.</param>
-        /// <param name="reader">The reader.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        private void AddImagesFromMusicNode(List<RemoteImageInfo> list, string releaseId, string releaseGroupId, XmlReader reader, CancellationToken cancellationToken)
-        {
-            reader.MoveToContent();
-
-            while (reader.Read())
-            {
-                if (reader.NodeType == XmlNodeType.Element)
-                {
-                    switch (reader.Name)
-                    {
-                        case "albums":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    AddImagesFromAlbumsNode(list, releaseId, releaseGroupId, subReader, cancellationToken);
-                                }
-                                break;
-                            }
-                        default:
-                            {
-                                using (reader.ReadSubtree())
-                                {
-                                }
-                                break;
-                            }
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Adds the images from albums node.
-        /// </summary>
-        /// <param name="list">The list.</param>
-        /// <param name="releaseId">The release identifier.</param>
-        /// <param name="releaseGroupId">The release group identifier.</param>
-        /// <param name="reader">The reader.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        private void AddImagesFromAlbumsNode(List<RemoteImageInfo> list, string releaseId, string releaseGroupId, XmlReader reader, CancellationToken cancellationToken)
-        {
-            reader.MoveToContent();
-
-            while (reader.Read())
-            {
-                if (reader.NodeType == XmlNodeType.Element)
-                {
-                    switch (reader.Name)
-                    {
-                        case "album":
-                            {
-                                var id = reader.GetAttribute("id");
-
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    if (string.Equals(id, releaseId, StringComparison.OrdinalIgnoreCase) ||
-                                        string.Equals(id, releaseGroupId, StringComparison.OrdinalIgnoreCase))
-                                    {
-                                        AddImages(list, subReader, cancellationToken);
-                                    }
-                                }
-                                break;
-                            }
-                        default:
-                            {
-                                using (reader.ReadSubtree())
-                                {
-                                }
-                                break;
-                            }
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Adds the images.
-        /// </summary>
-        /// <param name="list">The list.</param>
-        /// <param name="reader">The reader.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        private void AddImages(List<RemoteImageInfo> list, XmlReader reader, CancellationToken cancellationToken)
-        {
-            reader.MoveToContent();
-
-            while (reader.Read())
-            {
-                if (reader.NodeType == XmlNodeType.Element)
-                {
-                    switch (reader.Name)
-                    {
-                        case "cdart":
-                            {
-                                AddImage(list, reader, ImageType.Disc, 1000, 1000);
-                                break;
-                            }
-                        case "albumcover":
-                            {
-                                AddImage(list, reader, ImageType.Primary, 1000, 1000);
-                                break;
-                            }
-                        default:
-                            {
-                                using (reader.ReadSubtree())
-                                {
-                                }
-                                break;
-                            }
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Adds the image.
-        /// </summary>
-        /// <param name="list">The list.</param>
-        /// <param name="reader">The reader.</param>
-        /// <param name="type">The type.</param>
-        /// <param name="width">The width.</param>
-        /// <param name="height">The height.</param>
-        private void AddImage(List<RemoteImageInfo> list, XmlReader reader, ImageType type, int width, int height)
-        {
-            var url = reader.GetAttribute("url");
-
-            var size = reader.GetAttribute("size");
-
-            if (!string.IsNullOrEmpty(size))
-            {
-                int sizeNum;
-                if (int.TryParse(size, NumberStyles.Any, _usCulture, out sizeNum))
-                {
-                    width = sizeNum;
-                    height = sizeNum;
-                }
-            }
-
-            var likesString = reader.GetAttribute("likes");
-            int likes;
-
-            var info = new RemoteImageInfo
-            {
-                RatingType = RatingType.Likes,
-                Type = type,
-                Width = width,
-                Height = height,
-                ProviderName = Name,
-                Url = url,
-                Language = reader.GetAttribute("lang")
-            };
-
-            if (!string.IsNullOrEmpty(likesString) && int.TryParse(likesString, NumberStyles.Any, _usCulture, out likes))
-            {
-                info.CommunityRating = likes;
-            }
-
-            list.Add(info);
-        }
-
-        public int Order
-        {
-            get { return 0; }
-        }
-
-        public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
-        {
-            return _httpClient.GetResponse(new HttpRequestOptions
-            {
-                CancellationToken = cancellationToken,
-                Url = url,
-                ResourcePool = FanartArtistProvider.FanArtResourcePool
-            });
-        }
-    }
-}

+ 0 - 150
MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs

@@ -1,150 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.Music
-{
-    public class ManualLastFmImageProvider : IRemoteImageProvider
-    {
-        private readonly IHttpClient _httpClient;
-        private readonly IServerConfigurationManager _config;
-
-        public ManualLastFmImageProvider(IHttpClient httpClient, IServerConfigurationManager config)
-        {
-            _httpClient = httpClient;
-            _config = config;
-        }
-
-        public string Name
-        {
-            get { return ProviderName; }
-        }
-
-        public static string ProviderName
-        {
-            get { return "last.fm"; }
-        }
-
-        public bool Supports(IHasImages item)
-        {
-            return item is MusicAlbum || item is MusicArtist;
-        }
-
-        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
-        {
-            return new List<ImageType>
-            {
-                ImageType.Primary
-            };
-        }
-
-        public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
-        {
-            var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
-
-            return images.Where(i => i.Type == imageType);
-        }
-
-        public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
-        {
-            var list = new List<RemoteImageInfo>();
-
-            RemoteImageInfo info = null;
-
-            var musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz);
-
-            var album = item as MusicAlbum;
-            if (album != null)
-            {
-                info = GetInfo(album.LastFmImageUrl, album.LastFmImageSize);
-            }
-
-            var musicArtist = item as MusicArtist;
-            if (musicArtist != null && !string.IsNullOrEmpty(musicBrainzId))
-            {
-                var cachePath = Path.Combine(_config.ApplicationPaths.CachePath, "lastfm", musicBrainzId, "image.txt");
-
-                try
-                {
-                    var parts = File.ReadAllText(cachePath).Split('|');
-
-                    info = GetInfo(parts.FirstOrDefault(), parts.LastOrDefault());
-                }
-                catch (DirectoryNotFoundException ex)
-                {
-                }
-                catch (FileNotFoundException ex)
-                {
-                }
-            
-            }
-
-            if (info != null)
-            {
-                list.Add(info);
-            }
-
-            // The only info we have is size
-            return Task.FromResult<IEnumerable<RemoteImageInfo>>(list.OrderByDescending(i => i.Width ?? 0));
-        }
-
-        private RemoteImageInfo GetInfo(string url, string size)
-        {
-            if (string.IsNullOrEmpty(url))
-            {
-                return null;
-            }
-
-            var info = new RemoteImageInfo
-            {
-                ProviderName = Name,
-                Url = url,
-                Type = ImageType.Primary
-            };
-
-            if (string.Equals(size, "mega", StringComparison.OrdinalIgnoreCase))
-            {
-                
-            }
-            else if (string.Equals(size, "extralarge", StringComparison.OrdinalIgnoreCase))
-            {
-
-            }
-            else if (string.Equals(size, "large", StringComparison.OrdinalIgnoreCase))
-            {
-
-            }
-            else if (string.Equals(size, "medium", StringComparison.OrdinalIgnoreCase))
-            {
-
-            }
-
-            return info;
-        }
-
-        public int Order
-        {
-            get { return 1; }
-        }
-
-        public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
-        {
-            return _httpClient.GetResponse(new HttpRequestOptions
-            {
-                CancellationToken = cancellationToken,
-                Url = url,
-                ResourcePool = LastFmArtistProvider.LastfmResourcePool
-            });
-        }
-    }
-}

+ 31 - 34
MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs

@@ -1,11 +1,8 @@
 using MediaBrowser.Common;
 using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
 using System;
 using System.IO;
 using System.Net;
@@ -16,46 +13,46 @@ using System.Xml;
 
 namespace MediaBrowser.Providers.Music
 {
-    public class MusicBrainzAlbumProvider : BaseMetadataProvider
+    public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum>, IHasOrder
     {
         internal static MusicBrainzAlbumProvider Current;
 
         private readonly IHttpClient _httpClient;
         private readonly IApplicationHost _appHost;
 
-        public MusicBrainzAlbumProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IHttpClient httpClient, IApplicationHost appHost)
-            : base(logManager, configurationManager)
+        public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost)
         {
             _httpClient = httpClient;
             _appHost = appHost;
-
             Current = this;
         }
 
-        public override bool Supports(BaseItem item)
+        public async Task<MetadataResult<MusicAlbum>> GetMetadata(ItemId id, CancellationToken cancellationToken)
         {
-            return item is MusicAlbum;
-        }
+            var albumId = (AlbumId)id;
+            var releaseId = albumId.GetProviderId(MetadataProviders.Musicbrainz);
+            var releaseGroupId = albumId.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
 
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            var releaseId = item.GetProviderId(MetadataProviders.Musicbrainz);
-            var releaseGroupId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+            var result = new MetadataResult<MusicAlbum>();
 
             if (string.IsNullOrEmpty(releaseId))
             {
-                var result = await GetReleaseResult((MusicAlbum)item, cancellationToken).ConfigureAwait(false);
+                var releaseResult = await GetReleaseResult(albumId.ArtistMusicBrainzId, albumId.AlbumArtist, albumId.Name, cancellationToken).ConfigureAwait(false);
 
-                if (!string.IsNullOrEmpty(result.ReleaseId))
+                result.Item = new MusicAlbum();
+
+                if (!string.IsNullOrEmpty(releaseResult.ReleaseId))
                 {
-                    releaseId = result.ReleaseId;
-                    item.SetProviderId(MetadataProviders.Musicbrainz, releaseId);
+                    releaseId = releaseResult.ReleaseId;
+                    result.HasMetadata = true;
+                    result.Item.SetProviderId(MetadataProviders.Musicbrainz, releaseId);
                 }
 
-                if (!string.IsNullOrEmpty(result.ReleaseGroupId))
+                if (!string.IsNullOrEmpty(releaseResult.ReleaseGroupId))
                 {
-                    releaseGroupId = result.ReleaseGroupId;
-                    item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId);
+                    releaseGroupId = releaseResult.ReleaseGroupId;
+                    result.HasMetadata = true;
+                    result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId);
                 }
             }
 
@@ -63,25 +60,26 @@ namespace MediaBrowser.Providers.Music
             if (!string.IsNullOrEmpty(releaseId) && string.IsNullOrEmpty(releaseGroupId))
             {
                 releaseGroupId = await GetReleaseGroupId(releaseId, cancellationToken).ConfigureAwait(false);
-
-                item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId);
+                result.HasMetadata = true;
+                result.Item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, releaseGroupId);
             }
 
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
+            return result;
         }
 
-        private Task<ReleaseResult> GetReleaseResult(MusicAlbum album, CancellationToken cancellationToken)
+        public string Name
         {
-            var artist = album.Parent;
-            var artistId = artist.GetProviderId(MetadataProviders.Musicbrainz);
+            get { return "MusicBrainz"; }
+        }
 
-            if (!string.IsNullOrEmpty(artistId))
+        private Task<ReleaseResult> GetReleaseResult(string artistMusicBrainId, string artistName, string albumName, CancellationToken cancellationToken)
+        {
+            if (!string.IsNullOrEmpty(artistMusicBrainId))
             {
-                return GetReleaseResult(album.Name, artistId, cancellationToken);
+                return GetReleaseResult(albumName, artistMusicBrainId, cancellationToken);
             }
 
-            return GetReleaseResultByArtistName(album.Name, artist.Name, cancellationToken);
+            return GetReleaseResultByArtistName(albumName, artistName, cancellationToken);
         }
 
         private async Task<ReleaseResult> GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken)
@@ -218,10 +216,9 @@ namespace MediaBrowser.Providers.Music
             }
         }
 
-
-        public override MetadataProviderPriority Priority
+        public int Order
         {
-            get { return MetadataProviderPriority.Third; }
+            get { return 0; }
         }
     }
 }

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

@@ -11,7 +11,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.MusicGenres
 {
-    public class MusicGenreMetadataService : ConcreteMetadataService<MusicGenre>
+    public class MusicGenreMetadataService : ConcreteMetadataService<MusicGenre, ItemId>
     {
         private readonly ILibraryManager _libraryManager;
 

+ 1 - 1
MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs

@@ -14,7 +14,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.People
 {
-    public class MovieDbPersonImageProvider : IRemoteImageProvider
+    public class MovieDbPersonImageProvider : IRemoteImageProvider, IHasOrder
     {
         private readonly IServerConfigurationManager _config;
         private readonly IJsonSerializer _jsonSerializer;

+ 1 - 1
MediaBrowser.Providers/People/PersonMetadataService.cs

@@ -11,7 +11,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.People
 {
-    public class PersonMetadataService : ConcreteMetadataService<Person>
+    public class PersonMetadataService : ConcreteMetadataService<Person, ItemId>
     {
         private readonly ILibraryManager _libraryManager;
 

+ 1 - 1
MediaBrowser.Providers/People/TvdbPersonImageProvider.cs

@@ -18,7 +18,7 @@ using System.Xml;
 
 namespace MediaBrowser.Providers.People
 {
-    public class TvdbPersonImageProvider : IRemoteImageProvider
+    public class TvdbPersonImageProvider : IRemoteImageProvider, IHasOrder
     {
         private readonly IServerConfigurationManager _config;
         private readonly ILibraryManager _library;

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

@@ -11,7 +11,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Studios
 {
-    public class StudioMetadataService : ConcreteMetadataService<Studio>
+    public class StudioMetadataService : ConcreteMetadataService<Studio, ItemId>
     {
         private readonly ILibraryManager _libraryManager;
 

+ 1 - 1
MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs

@@ -127,7 +127,7 @@ namespace MediaBrowser.Providers.TV
 
                     var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
 
-                    var updates = _jsonSerializer.DeserializeFromString<List<FanArtUpdatesPrescanTask.FanArtUpdate>>(json);
+                    var updates = _jsonSerializer.DeserializeFromString<List<FanartUpdatesPrescanTask.FanArtUpdate>>(json);
 
                     return updates.Select(i => i.id).Where(existingDictionary.ContainsKey);
                 }

+ 1 - 1
MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs

@@ -19,7 +19,7 @@ using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.TV
 {
-    public class ManualFanartSeasonImageProvider : IRemoteImageProvider
+    public class ManualFanartSeasonImageProvider : IRemoteImageProvider, IHasOrder
     {
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly IServerConfigurationManager _config;

+ 1 - 1
MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs

@@ -19,7 +19,7 @@ using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.TV
 {
-    public class ManualFanartSeriesImageProvider : IRemoteImageProvider
+    public class ManualFanartSeriesImageProvider : IRemoteImageProvider, IHasOrder
     {
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly IServerConfigurationManager _config;

+ 1 - 1
MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs

@@ -19,7 +19,7 @@ using System.Xml;
 
 namespace MediaBrowser.Providers.TV
 {
-    public class ManualTvdbSeasonImageProvider : IRemoteImageProvider
+    public class ManualTvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
     {
         private readonly IServerConfigurationManager _config;
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");

+ 1 - 1
MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs

@@ -19,7 +19,7 @@ using System.Xml;
 
 namespace MediaBrowser.Providers.TV
 {
-    public class ManualTvdbSeriesImageProvider : IRemoteImageProvider
+    public class ManualTvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
     {
         private readonly IServerConfigurationManager _config;
         private readonly IHttpClient _httpClient;

+ 1 - 1
MediaBrowser.Providers/Users/UserMetadataService.cs

@@ -11,7 +11,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Users
 {
-    public class UserMetadataService : ConcreteMetadataService<User>
+    public class UserMetadataService : ConcreteMetadataService<User, ItemId>
     {
         private readonly IUserManager _userManager;