Преглед на файлове

convert artist providers

Luke Pulverenti преди 11 години
родител
ревизия
9685b81db5
променени са 37 файла, в които са добавени 847 реда и са изтрити 1127 реда
  1. 1 1
      MediaBrowser.Api/Images/ImageService.cs
  2. 1 1
      MediaBrowser.Api/Images/RemoteImageService.cs
  3. 0 7
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  4. 1 1
      MediaBrowser.Controller/Entities/BaseItem.cs
  5. 6 1
      MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
  6. 29 1
      MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
  7. 0 1
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  8. 38 1
      MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
  9. 7 38
      MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs
  10. 0 98
      MediaBrowser.Providers/FanartBaseProvider.cs
  11. 13 2
      MediaBrowser.Providers/Manager/MetadataService.cs
  12. 4 6
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  13. 6 5
      MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
  14. 4 4
      MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs
  15. 25 4
      MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs
  16. 0 91
      MediaBrowser.Providers/Music/ArtistInfoFromSongProvider.cs
  17. 67 0
      MediaBrowser.Providers/Music/ArtistMetadataService.cs
  18. 0 92
      MediaBrowser.Providers/Music/ArtistProviderFromXml.cs
  19. 59 0
      MediaBrowser.Providers/Music/ArtistXmlProvider.cs
  20. 3 4
      MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
  21. 350 208
      MediaBrowser.Providers/Music/FanArtArtistProvider.cs
  22. 9 15
      MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs
  23. 2 2
      MediaBrowser.Providers/Music/LastFmImageProvider.cs
  24. 4 4
      MediaBrowser.Providers/Music/LastfmAlbumProvider.cs
  25. 115 118
      MediaBrowser.Providers/Music/LastfmArtistProvider.cs
  26. 0 5
      MediaBrowser.Providers/Music/LastfmBaseProvider.cs
  27. 1 30
      MediaBrowser.Providers/Music/LastfmHelper.cs
  28. 2 3
      MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs
  29. 0 367
      MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs
  30. 24 4
      MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs
  31. 59 0
      MediaBrowser.Providers/ProviderUtils.cs
  32. 3 2
      MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
  33. 6 5
      MediaBrowser.Providers/TV/FanArtTVProvider.cs
  34. 3 3
      MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs
  35. 2 1
      MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs
  36. 2 1
      MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
  37. 1 1
      MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs

+ 1 - 1
MediaBrowser.Api/Images/ImageService.cs

@@ -801,7 +801,7 @@ namespace MediaBrowser.Api.Images
 
                 await entity.RefreshMetadata(new MetadataRefreshOptions
                 {
-                    ImageRefreshMode = MetadataRefreshMode.None,
+                    ImageRefreshMode = ImageRefreshMode.ValidationOnly,
                     ForceSave = true
 
                 }, CancellationToken.None).ConfigureAwait(false);

+ 1 - 1
MediaBrowser.Api/Images/RemoteImageService.cs

@@ -284,7 +284,7 @@ namespace MediaBrowser.Api.Images
             await item.RefreshMetadata(new MetadataRefreshOptions
             {
                 ForceSave = true,
-                ImageRefreshMode = MetadataRefreshMode.None,
+                ImageRefreshMode = ImageRefreshMode.ValidationOnly,
                 MetadataRefreshMode = MetadataRefreshMode.None
 
             }, CancellationToken.None).ConfigureAwait(false);

+ 0 - 7
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -69,13 +69,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             return base.GetClientTypeName();
         }
 
-        /// <summary>
-        /// Gets or sets the last fm image URL.
-        /// </summary>
-        /// <value>The last fm image URL.</value>
-        public string LastFmImageUrl { get; set; }
-        public string LastFmImageSize { get; set; }
-
         public MusicArtist()
         {
             UserItemCountList = new List<ItemByNameCounts>();

+ 1 - 1
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -1492,7 +1492,7 @@ namespace MediaBrowser.Controller.Entities
             return RefreshMetadata(new MetadataRefreshOptions
             {
                 ForceSave = true,
-                ImageRefreshMode = MetadataRefreshMode.None,
+                ImageRefreshMode = ImageRefreshMode.ValidationOnly,
                 MetadataRefreshMode = MetadataRefreshMode.None
 
             }, CancellationToken.None);

+ 6 - 1
MediaBrowser.Controller/MediaInfo/FFMpegManager.cs

@@ -126,7 +126,7 @@ namespace MediaBrowser.Controller.MediaInfo
         {
             if (!IsEligibleForChapterImageExtraction(video))
             {
-                return true;
+                extractImages = false;
             }
 
             var success = true;
@@ -187,6 +187,11 @@ namespace MediaBrowser.Controller.MediaInfo
                             break;
                         }
                     }
+                    else if (!string.IsNullOrEmpty(chapter.ImagePath))
+                    {
+                        chapter.ImagePath = null;
+                        changesMade = true;
+                    }
                 }
                 else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase))
                 {

+ 29 - 1
MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs

@@ -22,11 +22,21 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         [Obsolete]
         public bool ResetResolveArgs { get; set; }
+
+        public MetadataRefreshOptions()
+        {
+            ResetResolveArgs = true;
+        }
     }
 
     public class ImageRefreshOptions
     {
-        public MetadataRefreshMode ImageRefreshMode { get; set; }
+        public ImageRefreshMode ImageRefreshMode { get; set; }
+
+        public ImageRefreshOptions()
+        {
+            ImageRefreshMode = ImageRefreshMode.Default;
+        }
     }
 
     public enum MetadataRefreshMode
@@ -46,4 +56,22 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         FullRefresh
     }
+
+    public enum ImageRefreshMode
+    {
+        /// <summary>
+        /// The default
+        /// </summary>
+        Default,
+
+        /// <summary>
+        /// Existing images will be validated
+        /// </summary>
+        ValidationOnly,
+
+        /// <summary>
+        /// All providers will be executed to search for new metadata
+        /// </summary>
+        FullRefresh
+    }
 }

+ 0 - 1
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -245,7 +245,6 @@ namespace MediaBrowser.Model.Configuration
             EnableHttpLevelLogging = true;
             EnableDashboardResponseCaching = true;
 
-            EnableFanArtUpdates = true;
             EnableVideoImageExtraction = true;
             EnableMovieChapterImageExtraction = true;
             EnableEpisodeChapterImageExtraction = false;

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

@@ -1,11 +1,15 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Localization;
 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;
 
@@ -14,11 +18,13 @@ namespace MediaBrowser.Providers.BoxSets
     public class BoxSetMetadataService : ConcreteMetadataService<BoxSet>
     {
         private readonly ILibraryManager _libraryManager;
+        private readonly ILocalizationManager _iLocalizationManager;
 
-        public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
+        public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager, ILocalizationManager iLocalizationManager)
             : base(serverConfigurationManager, logger, providerManager, providerRepo)
         {
             _libraryManager = libraryManager;
+            _iLocalizationManager = iLocalizationManager;
         }
 
         /// <summary>
@@ -37,5 +43,36 @@ namespace MediaBrowser.Providers.BoxSets
         {
             return _libraryManager.UpdateItem(item, reason, cancellationToken);
         }
+
+        protected override ItemUpdateType AfterMetadataRefresh(BoxSet item)
+        {
+            var updateType = base.AfterMetadataRefresh(item);
+
+            if (!item.LockedFields.Contains(MetadataFields.OfficialRating))
+            {
+                var currentOfficialRating = item.OfficialRating;
+
+                // Gather all possible ratings
+                var ratings = item.RecursiveChildren
+                    .Concat(item.GetLinkedChildren())
+                    .Where(i => i is Movie || i is Series)
+                    .Select(i => i.OfficialRating)
+                    .Where(i => !string.IsNullOrEmpty(i))
+                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                    .Select(i => new Tuple<string, int?>(i, _iLocalizationManager.GetRatingLevel(i)))
+                    .OrderBy(i => i.Item2 ?? 1000)
+                    .Select(i => i.Item1);
+
+                item.OfficialRating = ratings.FirstOrDefault() ?? item.OfficialRating;
+
+                if (!string.Equals(currentOfficialRating ?? string.Empty, item.OfficialRating ?? string.Empty,
+                    StringComparison.OrdinalIgnoreCase))
+                {
+                    updateType = updateType | ItemUpdateType.MetadataDownload;
+                }
+            }
+
+            return updateType;
+        }
     }
 }

+ 7 - 38
MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs

@@ -1,29 +1,25 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
 using MediaBrowser.Providers.Movies;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.BoxSets
 {
     class MovieDbBoxSetImageProvider : IRemoteImageProvider
     {
-        private readonly IJsonSerializer _jsonSerializer;
         private readonly IHttpClient _httpClient;
 
-        public MovieDbBoxSetImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
+        public MovieDbBoxSetImageProvider(IHttpClient httpClient)
         {
-            _jsonSerializer = jsonSerializer;
             _httpClient = httpClient;
         }
 
@@ -163,33 +159,6 @@ namespace MediaBrowser.Providers.BoxSets
                 .ThenByDescending(i => i.vote_count);
         }
 
-        /// <summary>
-        /// Fetches the images.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="jsonSerializer">The json serializer.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{MovieImages}.</returns>
-        private async Task<MovieDbProvider.Images> FetchImages(BaseItem item, IJsonSerializer jsonSerializer,
-            CancellationToken cancellationToken)
-        {
-            await MovieDbProvider.Current.EnsureMovieInfo(item, cancellationToken).ConfigureAwait(false);
-
-            var path = MovieDbProvider.Current.GetDataFilePath(item);
-
-            if (!string.IsNullOrEmpty(path))
-            {
-                var fileInfo = new FileInfo(path);
-
-                if (fileInfo.Exists)
-                {
-                    return jsonSerializer.DeserializeFromFile<MovieDbProvider.CompleteMovieData>(path).images;
-                }
-            }
-
-            return null;
-        }
-
         public int Order
         {
             get { return 0; }

+ 0 - 98
MediaBrowser.Providers/FanartBaseProvider.cs

@@ -1,98 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Logging;
-using System.Collections.Generic;
-using System.Threading;
-
-namespace MediaBrowser.Providers
-{
-    /// <summary>
-    /// Class FanartBaseProvider
-    /// </summary>
-    public abstract class FanartBaseProvider : BaseMetadataProvider
-    {
-        internal static readonly SemaphoreSlim FanArtResourcePool = new SemaphoreSlim(3, 3);
-
-        /// <summary>
-        /// The LOG o_ FILE
-        /// </summary>
-        protected const string LogoFile = "logo.png";
-
-        /// <summary>
-        /// The AR t_ FILE
-        /// </summary>
-        protected const string ArtFile = "clearart.png";
-
-        /// <summary>
-        /// The THUM b_ FILE
-        /// </summary>
-        protected const string ThumbFile = "thumb.jpg";
-
-        /// <summary>
-        /// The DIS c_ FILE
-        /// </summary>
-        protected const string DiscFile = "disc.png";
-
-        /// <summary>
-        /// The BANNE r_ FILE
-        /// </summary>
-        protected const string BannerFile = "banner.png";
-
-        /// <summary>
-        /// The Backdrop
-        /// </summary>
-        protected const string BackdropFile = "backdrop.jpg";
-
-        /// <summary>
-        /// The Primary image
-        /// </summary>
-        protected const string PrimaryFile = "folder.jpg";
-
-        /// <summary>
-        /// The API key
-        /// </summary>
-        internal const string ApiKey = "5c6b04c68e904cfed1e6cbc9a9e683d4";
-
-        protected FanartBaseProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
-            : base(logManager, configurationManager)
-        {
-        }
-
-        /// <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; }
-        }
-
-        #region Result Objects
-
-        protected class FanArtImageInfo
-        {
-            public string id { get; set; }
-            public string url { get; set; }
-            public string likes { get; set; }
-        }
-
-        protected class FanArtMusicInfo
-        {
-            public string mbid_id { get; set; }
-            public List<FanArtImageInfo> musiclogo { get; set; }
-            public List<FanArtImageInfo> artistbackground { get; set; }
-            public List<FanArtImageInfo> artistthumb { get; set; }
-            public List<FanArtImageInfo> hdmusiclogo { get; set; }
-            public List<FanArtImageInfo> musicbanner { get; set; }
-        }
-
-        protected class FanArtMusicResult
-        {
-            public FanArtMusicInfo result { get; set; }
-        }
-
-        #endregion
-
-    }
-
-}

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

@@ -112,7 +112,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 != MetadataRefreshMode.None)
+            if (!localImagesFailed && options.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
             {
                 var providers = GetNonLocalImageProviders(item, lastResult.DateLastImagesRefresh.HasValue, options).ToList();
 
@@ -125,6 +125,8 @@ namespace MediaBrowser.Providers.Manager
                     refreshResult.SetDateLastImagesRefresh(DateTime.UtcNow);
                     refreshResult.AddImageProvidersRefreshed(result.Providers);
                 }
+
+                updateType = updateType | AfterMetadataRefresh(itemOfType);
             }
 
             var providersHadChanges = updateType > ItemUpdateType.Unspecified;
@@ -146,6 +148,15 @@ namespace MediaBrowser.Providers.Manager
             }
         }
 
+        /// <summary>
+        /// Afters the metadata refresh.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        protected virtual ItemUpdateType AfterMetadataRefresh(TItemType item)
+        {
+            return ItemUpdateType.Unspecified;
+        }
+
         /// <summary>
         /// Gets the providers.
         /// </summary>
@@ -200,7 +211,7 @@ namespace MediaBrowser.Providers.Manager
             }).ToList();
 
             // Run all if either of these flags are true
-            var runAllProviders = options.ImageRefreshMode == MetadataRefreshMode.FullRefresh || !hasRefreshedImages;
+            var runAllProviders = options.ImageRefreshMode == ImageRefreshMode.FullRefresh || !hasRefreshedImages;
 
             if (!runAllProviders)
             {

+ 4 - 6
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -80,7 +80,6 @@
     <Compile Include="Manager\MetadataService.cs" />
     <Compile Include="BaseXmlProvider.cs" />
     <Compile Include="CollectionFolderImageProvider.cs" />
-    <Compile Include="FanartBaseProvider.cs" />
     <Compile Include="FolderProviderFromXml.cs" />
     <Compile Include="Games\GameXmlParser.cs" />
     <Compile Include="Games\GameProviderFromXml.cs" />
@@ -101,6 +100,8 @@
     <Compile Include="Movies\ManualMovieDbImageProvider.cs" />
     <Compile Include="Movies\ManualFanartMovieImageProvider.cs" />
     <Compile Include="MusicGenres\MusicGenreMetadataService.cs" />
+    <Compile Include="Music\ArtistMetadataService.cs" />
+    <Compile Include="Music\LastFmArtistProvider.cs" />
     <Compile Include="People\MovieDbPersonImageProvider.cs" />
     <Compile Include="Movies\MovieUpdatesPrescanTask.cs" />
     <Compile Include="Movies\MovieXmlParser.cs" />
@@ -112,18 +113,15 @@
     <Compile Include="Movies\OpenMovieDatabaseProvider.cs" />
     <Compile Include="Music\AlbumInfoFromSongProvider.cs" />
     <Compile Include="Music\AlbumProviderFromXml.cs" />
-    <Compile Include="Music\ArtistInfoFromSongProvider.cs" />
-    <Compile Include="Music\ArtistProviderFromXml.cs" />
+    <Compile Include="Music\ArtistXmlProvider.cs" />
     <Compile Include="Music\FanArtAlbumProvider.cs" />
-    <Compile Include="Music\FanArtArtistProvider.cs" />
     <Compile Include="Music\FanArtUpdatesPrescanTask.cs" />
     <Compile Include="Music\LastfmAlbumProvider.cs" />
     <Compile Include="Music\LastFmImageProvider.cs" />
-    <Compile Include="Music\LastfmArtistProvider.cs" />
     <Compile Include="Music\LastfmBaseProvider.cs" />
     <Compile Include="Music\LastfmHelper.cs" />
     <Compile Include="Music\ManualFanartAlbumProvider.cs" />
-    <Compile Include="Music\ManualFanartArtistProvider.cs" />
+    <Compile Include="Music\FanartArtistProvider.cs" />
     <Compile Include="Music\ManualLastFmImageProvider.cs" />
     <Compile Include="Music\MusicBrainzAlbumProvider.cs" />
     <Compile Include="Music\MusicVideoXmlParser.cs" />

+ 6 - 5
MediaBrowser.Providers/Movies/FanArtMovieProvider.cs

@@ -17,13 +17,14 @@ using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Net;
 using System.Net;
+using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.Movies
 {
     /// <summary>
     /// Class FanArtMovieProvider
     /// </summary>
-    class FanArtMovieProvider : FanartBaseProvider
+    class FanArtMovieProvider : BaseMetadataProvider
     {
         /// <summary>
         /// Gets the HTTP client.
@@ -228,7 +229,7 @@ namespace MediaBrowser.Providers.Movies
         {
             cancellationToken.ThrowIfCancellationRequested();
 
-            var url = string.Format(FanArtBaseUrl, ApiKey, tmdbId);
+            var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tmdbId);
 
             var xmlPath = GetFanartXmlPath(tmdbId);
 
@@ -237,7 +238,7 @@ namespace MediaBrowser.Providers.Movies
             using (var response = await HttpClient.Get(new HttpRequestOptions
             {
                 Url = url,
-                ResourcePool = FanArtResourcePool,
+                ResourcePool = FanartArtistProvider.FanArtResourcePool,
                 CancellationToken = cancellationToken
 
             }).ConfigureAwait(false))
@@ -318,7 +319,7 @@ namespace MediaBrowser.Providers.Movies
             {
                 foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
                 {
-                    await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
+                    await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
                                         .ConfigureAwait(false);
 
                     if (item.BackdropImagePaths.Count >= backdropLimit) break;
@@ -332,7 +333,7 @@ namespace MediaBrowser.Providers.Movies
             {
                 try
                 {
-                    await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
+                    await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
                     break;
                 }
                 catch (HttpException ex)

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

@@ -54,7 +54,7 @@ namespace MediaBrowser.Providers.Movies
         /// <returns>Task.</returns>
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            if (!_config.Configuration.EnableInternetProviders)
+            if (!_config.Configuration.EnableInternetProviders || !_config.Configuration.EnableFanArtUpdates)
             {
                 progress.Report(100);
                 return;
@@ -101,18 +101,18 @@ namespace MediaBrowser.Providers.Movies
             // First get last time
             using (var stream = await _httpClient.Get(new HttpRequestOptions
             {
-                Url = string.Format(UpdatesUrl, FanartBaseProvider.ApiKey, lastUpdateTime),
+                Url = string.Format(UpdatesUrl, FanartArtistProvider.ApiKey, lastUpdateTime),
                 CancellationToken = cancellationToken,
                 EnableHttpCompression = true,
-                ResourcePool = FanartBaseProvider.FanArtResourcePool
+                ResourcePool = FanartArtistProvider.FanArtResourcePool
 
             }).ConfigureAwait(false))
             {
-                // If empty fanart will return a string of "null", rather than an empty list
                 using (var reader = new StreamReader(stream))
                 {
                     var json = await reader.ReadToEndAsync().ConfigureAwait(false);
 
+                    // If empty fanart will return a string of "null", rather than an empty list
                     if (string.Equals(json, "null", StringComparison.OrdinalIgnoreCase))
                     {
                         return new List<string>();

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

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
@@ -15,19 +16,22 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Xml;
+using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.Movies
 {
-    public class ManualFanartMovieImageProvider : IRemoteImageProvider
+    public class ManualFanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor
     {
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly IServerConfigurationManager _config;
         private readonly IHttpClient _httpClient;
+        private readonly IFileSystem _fileSystem;
 
-        public ManualFanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
+        public ManualFanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
         {
             _config = config;
             _httpClient = httpClient;
+            _fileSystem = fileSystem;
         }
 
         public string Name
@@ -329,8 +333,25 @@ namespace MediaBrowser.Providers.Movies
             {
                 CancellationToken = cancellationToken,
                 Url = url,
-                ResourcePool = FanartBaseProvider.FanArtResourcePool
+                ResourcePool = FanartArtistProvider.FanArtResourcePool
             });
         }
+
+        public bool HasChanged(IHasMetadata item, DateTime date)
+        {
+            var id = item.GetProviderId(MetadataProviders.Tmdb);
+
+            if (!string.IsNullOrEmpty(id))
+            {
+                // Process images
+                var xmlPath = FanArtMovieProvider.Current.GetFanartXmlPath(id);
+
+                var fileInfo = new FileInfo(xmlPath);
+
+                return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date;
+            }
+
+            return false;
+        }
     }
 }

+ 0 - 91
MediaBrowser.Providers/Music/ArtistInfoFromSongProvider.cs

@@ -1,91 +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 ArtistInfoFromSongProvider : BaseMetadataProvider
-    {
-        public ArtistInfoFromSongProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
-            : base(logManager, configurationManager)
-        {
-        }
-
-        public override bool Supports(BaseItem item)
-        {
-            return item is MusicArtist;
-        }
-
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var artist = (MusicArtist)item;
-
-            if (!artist.IsAccessedByName)
-            {
-                // If song metadata has changed
-                if (GetComparisonData(artist) != providerInfo.FileStamp)
-                {
-                    return true;
-                }
-            }
-
-            return base.NeedsRefreshInternal(item, providerInfo);
-        }
-        /// <summary>
-        /// Gets the data.
-        /// </summary>
-        /// <param name="artist">The artist.</param>
-        /// <returns>Guid.</returns>
-        private Guid GetComparisonData(MusicArtist artist)
-        {
-            var songs = artist.RecursiveChildren.OfType<Audio>().ToList();
-
-            return GetComparisonData(songs);
-        }
-
-        private Guid GetComparisonData(IEnumerable<Audio> songs)
-        {
-            var genres = songs.SelectMany(i => i.Genres)
-               .Distinct(StringComparer.OrdinalIgnoreCase)
-               .ToList();
-
-            return string.Join(string.Empty, genres.OrderBy(i => i).ToArray()).GetMD5();
-        }
-
-        public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            var artist = (MusicArtist)item;
-
-            if (!artist.IsAccessedByName)
-            {
-                var songs = artist.RecursiveChildren.OfType<Audio>().ToList();
-
-                if (!item.LockedFields.Contains(MetadataFields.Genres))
-                {
-                    artist.Genres = songs.SelectMany(i => i.Genres)
-                        .Distinct(StringComparer.OrdinalIgnoreCase)
-                        .ToList();
-                }
-
-                providerInfo.FileStamp = GetComparisonData(songs);
-            }
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return TrueTaskResult;
-        }
-
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.Second; }
-        }
-    }
-}

+ 67 - 0
MediaBrowser.Providers/Music/ArtistMetadataService.cs

@@ -0,0 +1,67 @@
+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 ArtistMetadataService : ConcreteMetadataService<MusicArtist>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public ArtistMetadataService(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(MusicArtist source, MusicArtist target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
+
+        protected override Task SaveItem(MusicArtist item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+
+        protected override ItemUpdateType AfterMetadataRefresh(MusicArtist item)
+        {
+            var updateType = base.AfterMetadataRefresh(item);
+
+            if (!item.IsAccessedByName && !item.LockedFields.Contains(MetadataFields.Genres))
+            {
+                var songs = item.RecursiveChildren.OfType<Audio>().ToList();
+
+                var currentGenres = 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))
+                {
+                    updateType = updateType | ItemUpdateType.MetadataDownload;
+                }
+            } 
+            
+            return updateType;
+        }
+    }
+}

+ 0 - 92
MediaBrowser.Providers/Music/ArtistProviderFromXml.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 ArtistProviderFromXml : BaseMetadataProvider
-    {
-        private readonly IFileSystem _fileSystem;
-
-        public ArtistProviderFromXml(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 MusicArtist) && 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 = "artist.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<MusicArtist>(Logger).Fetch((MusicArtist)item, path, cancellationToken);
-                }
-                finally
-                {
-                    XmlParsingResourcePool.Release();
-                }
-
-                SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-                return true;
-            }
-
-            return false;
-        }
-    }
-}

+ 59 - 0
MediaBrowser.Providers/Music/ArtistXmlProvider.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 ArtistXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicArtist>
+    {
+        private readonly ILogger _logger;
+
+        public ArtistXmlProvider(IFileSystem fileSystem, ILogger logger)
+            : base(fileSystem)
+        {
+            _logger = logger;
+        }
+
+        public async Task<MetadataResult<MusicArtist>> GetMetadata(string path, CancellationToken cancellationToken)
+        {
+            path = GetXmlPath(path);
+
+            var result = new MetadataResult<MusicArtist>();
+
+            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                var item = new MusicArtist();
+
+                new BaseItemXmlParser<MusicArtist>(_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, "artist.xml");
+        }
+    }
+}

+ 3 - 4
MediaBrowser.Providers/Music/FanArtAlbumProvider.cs

@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Music
     /// <summary>
     /// Class FanArtAlbumProvider
     /// </summary>
-    public class FanArtAlbumProvider : FanartBaseProvider
+    public class FanArtAlbumProvider : BaseMetadataProvider
     {
         /// <summary>
         /// The _provider manager
@@ -131,8 +131,7 @@ namespace MediaBrowser.Providers.Music
 
             if (!string.IsNullOrEmpty(artistMusicBrainzId))
             {
-                var artistXmlPath = FanArtArtistProvider.GetArtistDataPath(ConfigurationManager.CommonApplicationPaths, artistMusicBrainzId);
-                artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml");
+                var artistXmlPath = FanartArtistProvider.GetArtistXmlPath(ConfigurationManager.CommonApplicationPaths, artistMusicBrainzId);
 
                 var file = new FileInfo(artistXmlPath);
 
@@ -195,7 +194,7 @@ namespace MediaBrowser.Providers.Music
             {
                 try
                 {
-                    await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
+                    await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
                     break;
                 }
                 catch (HttpException ex)

+ 350 - 208
MediaBrowser.Providers/Music/FanArtArtistProvider.cs

@@ -4,237 +4,427 @@ 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 FanArtArtistProvider
-    /// </summary>
-    public class FanArtArtistProvider : FanartBaseProvider
+    public class FanartArtistProvider : IRemoteImageProvider, IHasChangeMonitor
     {
-        /// <summary>
-        /// Gets the HTTP client.
-        /// </summary>
-        /// <value>The HTTP client.</value>
-        protected IHttpClient HttpClient { get; private set; }
-
-        /// <summary>
-        /// The _provider manager
-        /// </summary>
-        private readonly IProviderManager _providerManager;
+        internal static readonly SemaphoreSlim FanArtResourcePool = new SemaphoreSlim(3, 3);
+        internal const string ApiKey = "5c6b04c68e904cfed1e6cbc9a9e683d4";
+        private const string FanArtBaseUrl = "http://api.fanart.tv/webservice/artist/{0}/{1}/xml/all/1/1";
 
-        internal static FanArtArtistProvider Current;
+        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="FanArtArtistProvider"/> 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 FanArtArtistProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
+        internal static FanartArtistProvider Current;
+
+        public FanartArtistProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
         {
-            if (httpClient == null)
-            {
-                throw new ArgumentNullException("httpClient");
-            }
-            HttpClient = httpClient;
-            _providerManager = providerManager;
+            _config = config;
+            _httpClient = httpClient;
             _fileSystem = fileSystem;
 
             Current = this;
         }
 
-        /// <summary>
-        /// The fan art base URL
-        /// </summary>
-        protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/artist/{0}/{1}/xml/all/1/1";
+        public string Name
+        {
+            get { return ProviderName; }
+        }
 
-        public override ItemUpdateType ItemUpdateType
+        public static string ProviderName
         {
-            get
-            {
-                return ItemUpdateType.ImageUpdate;
-            }
+            get { return "FanArt"; }
         }
-        
-        /// <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 MusicArtist;
         }
 
-        /// <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 IEnumerable<ImageType> GetSupportedImages(IHasImages item)
         {
-            get
+            return new List<ImageType>
             {
-                return true;
+                ImageType.Primary, 
+                ImageType.Logo,
+                ImageType.Art,
+                ImageType.Banner,
+                ImageType.Backdrop
+            };
+        }
+
+        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 async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
+        {
+            var artist = (MusicArtist)item;
+
+            var list = new List<RemoteImageInfo>();
+
+            var artistMusicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz);
+
+            if (!String.IsNullOrEmpty(artistMusicBrainzId))
+            {
+                await EnsureMovieXml(artistMusicBrainzId, cancellationToken).ConfigureAwait(false);
+
+                var artistXmlPath = GetArtistXmlPath(_config.CommonApplicationPaths, artistMusicBrainzId);
+
+                try
+                {
+                    AddImages(list, artistXmlPath, 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="cancellationToken">The cancellation token.</param>
+        private void AddImages(List<RemoteImageInfo> list, string xmlPath, CancellationToken cancellationToken)
         {
-            get
+            using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8))
             {
-                return "7";
+                // 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, subReader, cancellationToken);
+                                        }
+                                        break;
+                                    }
+
+                                default:
+                                    reader.Skip();
+                                    break;
+                            }
+                        }
+                    }
+                }
             }
         }
 
-        public override MetadataProviderPriority Priority
+        /// <summary>
+        /// Adds the images from music node.
+        /// </summary>
+        /// <param name="list">The list.</param>
+        /// <param name="reader">The reader.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        private void AddImagesFromMusicNode(List<RemoteImageInfo> list, XmlReader reader, CancellationToken cancellationToken)
         {
-            get
+            reader.MoveToContent();
+
+            while (reader.Read())
             {
-                return MetadataProviderPriority.Fourth;
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "hdmusiclogos":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    AddImagesFromImageTypeNode(list, ImageType.Logo, 800, 310, subReader, cancellationToken);
+                                }
+                                break;
+                            }
+                        case "musiclogos":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    AddImagesFromImageTypeNode(list, ImageType.Logo, 400, 155, subReader, cancellationToken);
+                                }
+                                break;
+                            }
+                        case "artistbackgrounds":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    AddImagesFromImageTypeNode(list, ImageType.Backdrop, 1920, 1080, subReader, cancellationToken);
+                                }
+                                break;
+                            }
+                        case "hdmusicarts":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    AddImagesFromImageTypeNode(list, ImageType.Art, 1000, 562, subReader, cancellationToken);
+                                }
+                                break;
+                            }
+                        case "musicarts":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    AddImagesFromImageTypeNode(list, ImageType.Art, 500, 281, subReader, cancellationToken);
+                                }
+                                break;
+                            }
+                        case "hdmusicbanners":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    AddImagesFromImageTypeNode(list, ImageType.Banner, 1000, 185, subReader, cancellationToken);
+                                }
+                                break;
+                            }
+                        case "musicbanners":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    AddImagesFromImageTypeNode(list, ImageType.Banner, 1000, 185, subReader, cancellationToken);
+                                }
+                                break;
+                            }
+                        case "artistthumbs":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    AddImagesFromImageTypeNode(list, ImageType.Primary, 1000, 1000, subReader, cancellationToken);
+                                }
+                                break;
+                            }
+                        default:
+                            {
+                                using (reader.ReadSubtree())
+                                {
+                                }
+                                break;
+                            }
+                    }
+                }
             }
         }
 
         /// <summary>
-        /// Needses the refresh internal.
+        /// Adds the images from albums 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="type">The type.</param>
+        /// <param name="width">The width.</param>
+        /// <param name="height">The height.</param>
+        /// <param name="reader">The reader.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        private void AddImagesFromImageTypeNode(List<RemoteImageInfo> list, ImageType type, int width, int height, XmlReader reader, CancellationToken cancellationToken)
         {
-            if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Musicbrainz)))
+            reader.MoveToContent();
+
+            while (reader.Read())
             {
-                return false;
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "hdmusiclogo":
+                        case "musiclogo":
+                        case "artistbackground":
+                        case "hdmusicart":
+                        case "musicart":
+                        case "hdmusicbanner":
+                        case "musicbanner":
+                        case "artistthumb":
+                            {
+                                AddImage(list, reader, type, width, height);
+                                break;
+                            }
+                        default:
+                            {
+                                using (reader.ReadSubtree())
+                                {
+                                }
+                                break;
+                            }
+                    }
+                }
             }
-
-            return base.NeedsRefreshInternal(item, providerInfo);
         }
 
-        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
+        /// <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 musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz);
+            var url = reader.GetAttribute("url");
 
-            if (!string.IsNullOrEmpty(musicBrainzId))
+            var size = reader.GetAttribute("size");
+
+            if (!String.IsNullOrEmpty(size))
             {
-                // Process images
-                var artistXmlPath = GetArtistDataPath(ConfigurationManager.CommonApplicationPaths, musicBrainzId);
-                artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml");
+                int sizeNum;
+                if (Int32.TryParse(size, NumberStyles.Any, _usCulture, out sizeNum))
+                {
+                    width = sizeNum;
+                    height = sizeNum;
+                }
+            }
 
-                var file = new FileInfo(artistXmlPath);
+            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")
+            };
 
-                return !file.Exists || _fileSystem.GetLastWriteTimeUtc(file) > providerInfo.LastRefreshed;
+            if (!String.IsNullOrEmpty(likesString) && Int32.TryParse(likesString, NumberStyles.Any, _usCulture, out likes))
+            {
+                info.CommunityRating = likes;
             }
 
-            return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
+            list.Add(info);
         }
 
-        /// <summary>
-        /// The us culture
-        /// </summary>
-        protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
-        /// <summary>
-        /// Gets the artist data path.
-        /// </summary>
-        /// <param name="appPaths">The application paths.</param>
-        /// <param name="musicBrainzArtistId">The music brainz artist identifier.</param>
-        /// <returns>System.String.</returns>
-        internal static string GetArtistDataPath(IApplicationPaths appPaths, string musicBrainzArtistId)
+        public int Order
         {
-            var dataPath = Path.Combine(GetArtistDataPath(appPaths), musicBrainzArtistId);
-
-            return dataPath;
+            get { return 0; }
         }
 
-        /// <summary>
-        /// Gets the artist data path.
-        /// </summary>
-        /// <param name="appPaths">The application paths.</param>
-        /// <returns>System.String.</returns>
-        internal static string GetArtistDataPath(IApplicationPaths appPaths)
+        public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            var dataPath = Path.Combine(appPaths.DataPath, "fanart-music");
-
-            return dataPath;
+            return _httpClient.GetResponse(new HttpRequestOptions
+            {
+                CancellationToken = cancellationToken,
+                Url = url,
+                ResourcePool = FanArtResourcePool
+            });
         }
 
-        /// <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 bool HasChanged(IHasMetadata item, DateTime date)
         {
-            cancellationToken.ThrowIfCancellationRequested();
+            var id = item.GetProviderId(MetadataProviders.Musicbrainz);
 
-            var musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz);
+            if (!String.IsNullOrEmpty(id))
+            {
+                // Process images
+                var artistXmlPath = GetArtistXmlPath(_config.CommonApplicationPaths, id);
 
-            var artistDataPath = GetArtistDataPath(ConfigurationManager.ApplicationPaths, musicBrainzId);
-            var xmlPath = Path.Combine(artistDataPath, "fanart.xml");
+                var fileInfo = new FileInfo(artistXmlPath);
 
-            // Only download the xml if it doesn't already exist. The prescan task will take care of getting updates
-            if (!File.Exists(xmlPath))
-            {
-                await DownloadArtistXml(artistDataPath, musicBrainzId, cancellationToken).ConfigureAwait(false);
+                return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date;
             }
 
-            if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art ||
-                ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops ||
-                ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner ||
-                ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo ||
-                ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary)
+            return false;
+        }
+
+        internal Task EnsureMovieXml(string musicBrainzId, CancellationToken cancellationToken)
+        {
+            var xmlPath = GetArtistXmlPath(_config.ApplicationPaths, musicBrainzId);
+
+            var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath);
+
+            if (fileInfo.Exists)
             {
-                var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartArtistProvider.ProviderName).ConfigureAwait(false);
-                await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false);
+                if (_config.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
+                {
+                    return Task.FromResult(true);
+                }
             }
 
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
+            return DownloadArtistXml(musicBrainzId, cancellationToken);
         }
 
         /// <summary>
         /// Downloads the artist XML.
         /// </summary>
-        /// <param name="artistPath">The artist path.</param>
         /// <param name="musicBrainzId">The music brainz id.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{System.Boolean}.</returns>
-        internal async Task DownloadArtistXml(string artistPath, string musicBrainzId, CancellationToken cancellationToken)
+        internal async Task DownloadArtistXml(string musicBrainzId, CancellationToken cancellationToken)
         {
             cancellationToken.ThrowIfCancellationRequested();
 
             var url = string.Format(FanArtBaseUrl, ApiKey, musicBrainzId);
 
-            var xmlPath = Path.Combine(artistPath, "fanart.xml");
+            var xmlPath = GetArtistXmlPath(_config.ApplicationPaths, musicBrainzId);
+
+            Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
 
-            Directory.CreateDirectory(artistPath);
-            
-            using (var response = await HttpClient.Get(new HttpRequestOptions
+            using (var response = await _httpClient.Get(new HttpRequestOptions
             {
                 Url = url,
                 ResourcePool = FanArtResourcePool,
@@ -250,83 +440,35 @@ namespace MediaBrowser.Providers.Music
         }
 
         /// <summary>
-        /// Fetches from XML.
+        /// Gets the artist data path.
         /// </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="appPaths">The application paths.</param>
+        /// <param name="musicBrainzArtistId">The music brainz artist identifier.</param>
+        /// <returns>System.String.</returns>
+        private static string GetArtistDataPath(IApplicationPaths appPaths, string musicBrainzArtistId)
         {
-            if (!item.LockedFields.Contains(MetadataFields.Images))
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary && !item.HasImage(ImageType.Primary))
-                {
-                    await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
-                }
-
-                cancellationToken.ThrowIfCancellationRequested();
-
-                if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo && !item.HasImage(ImageType.Logo))
-                {
-                    await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
-                }
-
-                cancellationToken.ThrowIfCancellationRequested();
-
-                if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art && !item.HasImage(ImageType.Art))
-                {
-                    await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
-                }
-
-                cancellationToken.ThrowIfCancellationRequested();
-
-                if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner && !item.HasImage(ImageType.Banner))
-                {
-                    await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
-                }
-            }
+            var dataPath = Path.Combine(GetArtistDataPath(appPaths), musicBrainzArtistId);
 
-            if (!item.LockedFields.Contains(MetadataFields.Backdrops))
-            {
-                cancellationToken.ThrowIfCancellationRequested();
+            return dataPath;
+        }
 
-                var backdropLimit = ConfigurationManager.Configuration.MusicOptions.MaxBackdrops;
-                if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops &&
-                    item.BackdropImagePaths.Count < backdropLimit)
-                {
-                    foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
-                    {
-                        await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
-                            .ConfigureAwait(false);
+        /// <summary>
+        /// Gets the artist data path.
+        /// </summary>
+        /// <param name="appPaths">The application paths.</param>
+        /// <returns>System.String.</returns>
+        internal static string GetArtistDataPath(IApplicationPaths appPaths)
+        {
+            var dataPath = Path.Combine(appPaths.DataPath, "fanart-music");
 
-                        if (item.BackdropImagePaths.Count >= backdropLimit) break;
-                    }
-                }
-            }
+            return dataPath;
         }
 
-        private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
+        internal static string GetArtistXmlPath(IApplicationPaths appPaths, string musicBrainzArtistId)
         {
-            foreach (var image in images.Where(i => i.Type == type))
-            {
-                try
-                {
-                    await _providerManager.SaveImage(item, image.Url, 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;
-                }
-            }
+            var dataPath = GetArtistDataPath(appPaths, musicBrainzArtistId);
+
+            return Path.Combine(dataPath, "fanart.xml");
         }
     }
 }

+ 9 - 15
MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs

@@ -53,13 +53,13 @@ namespace MediaBrowser.Providers.Music
         /// <returns>Task.</returns>
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            if (!_config.Configuration.EnableInternetProviders)
+            if (!_config.Configuration.EnableInternetProviders || !_config.Configuration.EnableFanArtUpdates)
             {
                 progress.Report(100);
                 return;
             }
 
-            var path = FanArtArtistProvider.GetArtistDataPath(_config.CommonApplicationPaths);
+            var path = FanartArtistProvider.GetArtistDataPath(_config.CommonApplicationPaths);
 
             Directory.CreateDirectory(path);
 
@@ -85,7 +85,7 @@ namespace MediaBrowser.Providers.Music
 
                 progress.Report(5);
 
-                await UpdateArtists(artistsToUpdate, path, progress, cancellationToken).ConfigureAwait(false);
+                await UpdateArtists(artistsToUpdate, progress, cancellationToken).ConfigureAwait(false);
             }
 
             var newUpdateTime = Convert.ToInt64(DateTimeToUnixTimestamp(DateTime.UtcNow)).ToString(UsCulture);
@@ -107,10 +107,10 @@ namespace MediaBrowser.Providers.Music
             // First get last time
             using (var stream = await _httpClient.Get(new HttpRequestOptions
             {
-                Url = string.Format(UpdatesUrl, FanartBaseProvider.ApiKey, lastUpdateTime),
+                Url = string.Format(UpdatesUrl, FanartArtistProvider.ApiKey, lastUpdateTime),
                 CancellationToken = cancellationToken,
                 EnableHttpCompression = true,
-                ResourcePool = FanartBaseProvider.FanArtResourcePool
+                ResourcePool = FanartArtistProvider.FanArtResourcePool
 
             }).ConfigureAwait(false))
             {
@@ -137,18 +137,17 @@ namespace MediaBrowser.Providers.Music
         /// Updates the artists.
         /// </summary>
         /// <param name="idList">The id list.</param>
-        /// <param name="artistsDataPath">The artists data path.</param>
         /// <param name="progress">The progress.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        private async Task UpdateArtists(IEnumerable<string> idList, string artistsDataPath, IProgress<double> progress, CancellationToken cancellationToken)
+        private async Task UpdateArtists(IEnumerable<string> idList, IProgress<double> progress, CancellationToken cancellationToken)
         {
             var list = idList.ToList();
             var numComplete = 0;
 
             foreach (var id in list)
             {
-                await UpdateArtist(id, artistsDataPath, cancellationToken).ConfigureAwait(false);
+                await UpdateArtist(id, cancellationToken).ConfigureAwait(false);
 
                 numComplete++;
                 double percent = numComplete;
@@ -163,18 +162,13 @@ namespace MediaBrowser.Providers.Music
         /// Updates the artist.
         /// </summary>
         /// <param name="musicBrainzId">The musicBrainzId.</param>
-        /// <param name="artistsDataPath">The artists data path.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        private Task UpdateArtist(string musicBrainzId, string artistsDataPath, CancellationToken cancellationToken)
+        private Task UpdateArtist(string musicBrainzId, CancellationToken cancellationToken)
         {
             _logger.Info("Updating artist " + musicBrainzId);
 
-            artistsDataPath = Path.Combine(artistsDataPath, musicBrainzId);
-
-            Directory.CreateDirectory(artistsDataPath);
-
-            return FanArtArtistProvider.Current.DownloadArtistXml(artistsDataPath, musicBrainzId, cancellationToken);
+            return FanartArtistProvider.Current.DownloadArtistXml(musicBrainzId, cancellationToken);
         }
 
         /// <summary>

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

@@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.Music
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         public override bool Supports(BaseItem item)
         {
-            return item is MusicArtist || item is MusicAlbum;
+            return item is MusicAlbum;
         }
 
         /// <summary>
@@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.Music
 
                 if (image != null)
                 {
-                    await _providerManager.SaveImage(item, image.Url, LastfmBaseProvider.LastfmResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
+                    await _providerManager.SaveImage(item, image.Url, LastFmArtistProvider.LastfmResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
                 }
             }
         }

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

@@ -144,12 +144,12 @@ namespace MediaBrowser.Providers.Music
         private async Task<LastfmGetAlbumResult> GetAlbumResult(string artist, string album, CancellationToken cancellationToken)
         {
             // Get albu info using artist and album name
-            var url = RootUrl + string.Format("method=album.getInfo&artist={0}&album={1}&api_key={2}&format=json", UrlEncode(artist), UrlEncode(album), 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
             {
                 Url = url,
-                ResourcePool = LastfmResourcePool,
+                ResourcePool = LastFmArtistProvider.LastfmResourcePool,
                 CancellationToken = cancellationToken,
                 EnableHttpCompression = false
 
@@ -170,12 +170,12 @@ namespace MediaBrowser.Providers.Music
         private async Task<LastfmGetAlbumResult> GetAlbumResult(string musicbraizId, CancellationToken cancellationToken)
         {
             // Get albu info using artist and album name
-            var url = RootUrl + string.Format("method=album.getInfo&mbid={0}&api_key={1}&format=json", musicbraizId, 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
             {
                 Url = url,
-                ResourcePool = LastfmResourcePool,
+                ResourcePool = LastFmArtistProvider.LastfmResourcePool,
                 CancellationToken = cancellationToken,
                 EnableHttpCompression = false
 

+ 115 - 118
MediaBrowser.Providers/Music/LastfmArtistProvider.cs

@@ -2,7 +2,6 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
@@ -20,69 +19,128 @@ using System.Xml;
 
 namespace MediaBrowser.Providers.Music
 {
-    /// <summary>
-    /// Class LastfmArtistProvider
-    /// </summary>
-    public class LastfmArtistProvider : LastfmBaseProvider
+    public class LastFmArtistProvider : IRemoteMetadataProvider<MusicArtist>
     {
-        /// <summary>
-        /// The _library manager
-        /// </summary>
-        protected readonly ILibraryManager LibraryManager;
+        private readonly IJsonSerializer _json;
+        private readonly IHttpClient _httpClient;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="LastfmArtistProvider" /> 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>
-        /// <param name="libraryManager">The library manager.</param>
-        public LastfmArtistProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, ILibraryManager libraryManager)
-            : base(jsonSerializer, httpClient, logManager, configurationManager)
+        internal static readonly SemaphoreSlim LastfmResourcePool = new SemaphoreSlim(4, 4);
+
+        internal const string RootUrl = @"http://ws.audioscrobbler.com/2.0/?";
+        internal static string ApiKey = "7b76553c3eb1d341d642755aecc40a33";
+
+        private readonly IServerConfigurationManager _config;
+        private ILogger _logger;
+
+        public LastFmArtistProvider(IHttpClient httpClient, IJsonSerializer json)
         {
-            LibraryManager = libraryManager;
+            _httpClient = httpClient;
+            _json = json;
         }
 
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+        public async Task<MetadataResult<MusicArtist>> GetMetadata(ItemId id, CancellationToken cancellationToken)
         {
-            if (HasAltMeta(item))
+            var result = new MetadataResult<MusicArtist>();
+
+            var musicBrainzId = id.GetProviderId(MetadataProviders.Musicbrainz) ?? await FindId(id, cancellationToken).ConfigureAwait(false);
+
+            if (!String.IsNullOrWhiteSpace(musicBrainzId))
             {
-                return false;
+                cancellationToken.ThrowIfCancellationRequested();
+
+                result.Item = new MusicArtist();
+                result.HasMetadata = true;
+
+                result.Item.SetProviderId(MetadataProviders.Musicbrainz, musicBrainzId);
+
+                await FetchLastfmData(result.Item, musicBrainzId, cancellationToken).ConfigureAwait(false);
             }
 
-            return base.NeedsRefreshInternal(item, providerInfo);
+            return result;
         }
 
-        protected override string ProviderVersion
+        protected virtual async Task FetchLastfmData(MusicArtist item, string musicBrainzId, CancellationToken cancellationToken)
         {
-            get
+            // Get artist info with provided id
+            var url = RootUrl + String.Format("method=artist.getInfo&mbid={0}&api_key={1}&format=json", UrlEncode(musicBrainzId), ApiKey);
+
+            LastfmGetArtistResult result;
+
+            using (var json = await _httpClient.Get(new HttpRequestOptions
+            {
+                Url = url,
+                ResourcePool = LastfmResourcePool,
+                CancellationToken = cancellationToken,
+                EnableHttpCompression = false
+
+            }).ConfigureAwait(false))
+            {
+                using (var reader = new StreamReader(json))
+                {
+                    var jsonText = await reader.ReadToEndAsync().ConfigureAwait(false);
+
+                    // Fix their bad json
+                    jsonText = jsonText.Replace("\"#text\"", "\"url\"");
+
+                    result = _json.DeserializeFromString<LastfmGetArtistResult>(jsonText);
+                }
+            }
+
+            if (result != null && result.artist != null)
             {
-                return "9";
+                ProcessArtistData(item, result.artist, musicBrainzId);
             }
         }
 
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
+        private void ProcessArtistData(MusicArtist artist, LastfmArtist data, string musicBrainzId)
         {
-            get { return MetadataProviderPriority.Third; }
-        }
+            var yearFormed = 0;
 
-        private bool HasAltMeta(BaseItem item)
-        {
-            return item.LocationType == LocationType.FileSystem && item.ResolveArgs.ContainsMetaFileByName("artist.xml");
-        }
+            if (data.bio != null)
+            {
+                Int32.TryParse(data.bio.yearformed, out yearFormed);
+                if (!artist.LockedFields.Contains(MetadataFields.Overview))
+                {
+                    artist.Overview = data.bio.content;
+                }
+                if (!string.IsNullOrEmpty(data.bio.placeformed) && !artist.LockedFields.Contains(MetadataFields.ProductionLocations))
+                {
+                    artist.AddProductionLocation(data.bio.placeformed);
+                }
+            }
 
-        /// <summary>
-        /// Finds the id.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.String}.</returns>
-        private async Task<string> FindId(BaseItem item, CancellationToken cancellationToken)
+            if (yearFormed > 0)
+            {
+                artist.PremiereDate = new DateTime(yearFormed, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+                artist.ProductionYear = yearFormed;
+            }
+
+            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);
+            }
+        }
+        
+        private async Task<string> FindId(ItemId item, CancellationToken cancellationToken)
         {
             try
             {
@@ -101,44 +159,18 @@ namespace MediaBrowser.Providers.Music
             }
         }
 
-        /// <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 id = item.GetProviderId(MetadataProviders.Musicbrainz) ?? await FindId(item, cancellationToken).ConfigureAwait(false);
-            
-            if (!string.IsNullOrWhiteSpace(id))
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                item.SetProviderId(MetadataProviders.Musicbrainz, id);
-
-                await FetchLastfmData(item, id, force, cancellationToken).ConfigureAwait(false);
-            }
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
-        }
-
         /// <summary>
         /// Finds the id from music brainz.
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{System.String}.</returns>
-        private async Task<string> FindIdFromMusicBrainz(BaseItem item, CancellationToken cancellationToken)
+        private async Task<string> FindIdFromMusicBrainz(ItemId item, CancellationToken cancellationToken)
         {
             // They seem to throw bad request failures on any term with a slash
             var nameToSearch = item.Name.Replace('/', ' ');
 
-            var url = string.Format("http://www.musicbrainz.org/ws/2/artist/?query=artist:\"{0}\"", UrlEncode(nameToSearch));
+            var url = String.Format("http://www.musicbrainz.org/ws/2/artist/?query=artist:\"{0}\"", UrlEncode(nameToSearch));
 
             var doc = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
 
@@ -154,7 +186,7 @@ namespace MediaBrowser.Providers.Music
             if (HasDiacritics(item.Name))
             {
                 // Try again using the search with accent characters url
-                url = string.Format("http://www.musicbrainz.org/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
+                url = String.Format("http://www.musicbrainz.org/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
 
                 doc = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
 
@@ -178,7 +210,7 @@ namespace MediaBrowser.Providers.Music
         /// <returns><c>true</c> if the specified text has diacritics; otherwise, <c>false</c>.</returns>
         private bool HasDiacritics(string text)
         {
-            return !string.Equals(text, RemoveDiacritics(text), StringComparison.Ordinal);
+            return !String.Equals(text, RemoveDiacritics(text), StringComparison.Ordinal);
         }
 
         /// <summary>
@@ -188,7 +220,7 @@ namespace MediaBrowser.Providers.Music
         /// <returns>System.String.</returns>
         private string RemoveDiacritics(string text)
         {
-            return string.Concat(
+            return String.Concat(
                 text.Normalize(NormalizationForm.FormD)
                 .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) !=
                                               UnicodeCategory.NonSpacingMark)
@@ -196,53 +228,18 @@ namespace MediaBrowser.Providers.Music
         }
 
         /// <summary>
-        /// Fetches the lastfm data.
+        /// Encodes an URL.
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="musicBrainzId">The music brainz id.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        protected virtual async Task FetchLastfmData(BaseItem item, string musicBrainzId, bool force, CancellationToken cancellationToken)
+        /// <param name="name">The name.</param>
+        /// <returns>System.String.</returns>
+        private string UrlEncode(string name)
         {
-            // Get artist info with provided id
-            var url = RootUrl + string.Format("method=artist.getInfo&mbid={0}&api_key={1}&format=json", UrlEncode(musicBrainzId), ApiKey);
-
-            LastfmGetArtistResult result;
-
-            using (var json = await HttpClient.Get(new HttpRequestOptions
-            {
-                Url = url,
-                ResourcePool = LastfmResourcePool,
-                CancellationToken = cancellationToken,
-                EnableHttpCompression = false
-
-            }).ConfigureAwait(false))
-            {
-                using (var reader = new StreamReader(json))
-                {
-                    var jsonText = await reader.ReadToEndAsync().ConfigureAwait(false);
-
-                    // Fix their bad json
-                    jsonText = jsonText.Replace("\"#text\"", "\"url\"");
-
-                    result = JsonSerializer.DeserializeFromString<LastfmGetArtistResult>(jsonText);
-                }
-            }
-
-            if (result != null && result.artist != null)
-            {
-                LastfmHelper.ProcessArtistData((MusicArtist)item, result.artist);
-            }
+            return WebUtility.UrlEncode(name);
         }
 
-        /// <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 string Name
         {
-            return item is MusicArtist;
+            get { return "last.fm"; }
         }
     }
 }

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

@@ -15,8 +15,6 @@ namespace MediaBrowser.Providers.Music
     /// </summary>
     public abstract class LastfmBaseProvider : BaseMetadataProvider
     {
-        internal static readonly SemaphoreSlim LastfmResourcePool = new SemaphoreSlim(4, 4);
-
         /// <summary>
         /// Initializes a new instance of the <see cref="LastfmBaseProvider" /> class.
         /// </summary>
@@ -80,9 +78,6 @@ namespace MediaBrowser.Providers.Music
             }
         }
 
-        protected const string RootUrl = @"http://ws.audioscrobbler.com/2.0/?";
-        protected static string ApiKey = "7b76553c3eb1d341d642755aecc40a33";
-
         /// <summary>
         /// Encodes an URL.
         /// </summary>

+ 1 - 30
MediaBrowser.Providers/Music/LastfmHelper.cs

@@ -8,36 +8,7 @@ namespace MediaBrowser.Providers.Music
 {
     public static class LastfmHelper
     {
-        public static void ProcessArtistData(MusicArtist artist, LastfmArtist data)
-        {
-            var yearFormed = 0;
-
-            if (data.bio != null)
-            {
-                Int32.TryParse(data.bio.yearformed, out yearFormed);
-                if (!artist.LockedFields.Contains(MetadataFields.Overview))
-                {
-                    artist.Overview = data.bio.content;
-                }
-                if (!string.IsNullOrEmpty(data.bio.placeformed) && !artist.LockedFields.Contains(MetadataFields.ProductionLocations))
-                {
-                    artist.AddProductionLocation(data.bio.placeformed);
-                }
-            }
-
-            if (yearFormed > 0)
-            {
-                artist.PremiereDate = new DateTime(yearFormed, 1, 1, 0, 0, 0, DateTimeKind.Utc);
-
-                artist.ProductionYear = yearFormed;
-            }
-
-            string imageSize;
-            artist.LastFmImageUrl = GetImageUrl(data, out imageSize);
-            artist.LastFmImageSize = imageSize;
-        }
-
-        private static string GetImageUrl(IHasLastFmImages data, out string size)
+        public static string GetImageUrl(IHasLastFmImages data, out string size)
         {
             size = null;
 

+ 2 - 3
MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs

@@ -71,8 +71,7 @@ namespace MediaBrowser.Providers.Music
 
             if (!string.IsNullOrEmpty(artistMusicBrainzId))
             {
-                var artistXmlPath = FanArtArtistProvider.GetArtistDataPath(_config.CommonApplicationPaths, artistMusicBrainzId);
-                artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml");
+                var artistXmlPath = FanartArtistProvider.GetArtistXmlPath(_config.CommonApplicationPaths, artistMusicBrainzId);
 
                 var musicBrainzReleaseGroupId = album.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
 
@@ -348,7 +347,7 @@ namespace MediaBrowser.Providers.Music
             {
                 CancellationToken = cancellationToken,
                 Url = url,
-                ResourcePool = FanartBaseProvider.FanArtResourcePool
+                ResourcePool = FanartArtistProvider.FanArtResourcePool
             });
         }
     }

+ 0 - 367
MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs

@@ -1,367 +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 ManualFanartArtistProvider : IRemoteImageProvider
-    {
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-        private readonly IServerConfigurationManager _config;
-        private readonly IHttpClient _httpClient;
-
-        public ManualFanartArtistProvider(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 MusicArtist;
-        }
-
-        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
-        {
-            return new List<ImageType>
-            {
-                ImageType.Primary, 
-                ImageType.Logo,
-                ImageType.Art,
-                ImageType.Banner,
-                ImageType.Backdrop
-            };
-        }
-
-        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 artist = (MusicArtist)item;
-
-            var list = new List<RemoteImageInfo>();
-
-            var artistMusicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz);
-
-            if (!string.IsNullOrEmpty(artistMusicBrainzId))
-            {
-                var artistXmlPath = FanArtArtistProvider.GetArtistDataPath(_config.CommonApplicationPaths, artistMusicBrainzId);
-                artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml");
-
-                try
-                {
-                    AddImages(list, artistXmlPath, 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="cancellationToken">The cancellation token.</param>
-        private void AddImages(List<RemoteImageInfo> list, string xmlPath, 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, subReader, cancellationToken);
-                                        }
-                                        break;
-                                    }
-
-                                default:
-                                    reader.Skip();
-                                    break;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Adds the images from music node.
-        /// </summary>
-        /// <param name="list">The list.</param>
-        /// <param name="reader">The reader.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        private void AddImagesFromMusicNode(List<RemoteImageInfo> list, XmlReader reader, CancellationToken cancellationToken)
-        {
-            reader.MoveToContent();
-
-            while (reader.Read())
-            {
-                if (reader.NodeType == XmlNodeType.Element)
-                {
-                    switch (reader.Name)
-                    {
-                        case "hdmusiclogos":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    AddImagesFromImageTypeNode(list, ImageType.Logo, 800, 310, subReader, cancellationToken);
-                                }
-                                break;
-                            }
-                        case "musiclogos":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    AddImagesFromImageTypeNode(list, ImageType.Logo, 400, 155, subReader, cancellationToken);
-                                }
-                                break;
-                            }
-                        case "artistbackgrounds":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    AddImagesFromImageTypeNode(list, ImageType.Backdrop, 1920, 1080, subReader, cancellationToken);
-                                }
-                                break;
-                            }
-                        case "hdmusicarts":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    AddImagesFromImageTypeNode(list, ImageType.Art, 1000, 562, subReader, cancellationToken);
-                                }
-                                break;
-                            }
-                        case "musicarts":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    AddImagesFromImageTypeNode(list, ImageType.Art, 500, 281, subReader, cancellationToken);
-                                }
-                                break;
-                            }
-                        case "hdmusicbanners":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    AddImagesFromImageTypeNode(list, ImageType.Banner, 1000, 185, subReader, cancellationToken);
-                                }
-                                break;
-                            }
-                        case "musicbanners":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    AddImagesFromImageTypeNode(list, ImageType.Banner, 1000, 185, subReader, cancellationToken);
-                                }
-                                break;
-                            }
-                        case "artistthumbs":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    AddImagesFromImageTypeNode(list, ImageType.Primary, 1000, 1000, subReader, cancellationToken);
-                                }
-                                break;
-                            }
-                        default:
-                            {
-                                using (reader.ReadSubtree())
-                                {
-                                }
-                                break;
-                            }
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Adds the images from albums node.
-        /// </summary>
-        /// <param name="list">The list.</param>
-        /// <param name="type">The type.</param>
-        /// <param name="width">The width.</param>
-        /// <param name="height">The height.</param>
-        /// <param name="reader">The reader.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        private void AddImagesFromImageTypeNode(List<RemoteImageInfo> list, ImageType type, int width, int height, XmlReader reader, CancellationToken cancellationToken)
-        {
-            reader.MoveToContent();
-
-            while (reader.Read())
-            {
-                if (reader.NodeType == XmlNodeType.Element)
-                {
-                    switch (reader.Name)
-                    {
-                        case "hdmusiclogo":
-                        case "musiclogo":
-                        case "artistbackground":
-                        case "hdmusicart":
-                        case "musicart":
-                        case "hdmusicbanner":
-                        case "musicbanner":
-                        case "artistthumb":
-                            {
-                                AddImage(list, reader, type, width, height);
-                                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 = FanartBaseProvider.FanArtResourcePool
-            });
-        }
-    }
-}

+ 24 - 4
MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Providers;
@@ -6,6 +7,7 @@ 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;
@@ -15,10 +17,12 @@ namespace MediaBrowser.Providers.Music
     public class ManualLastFmImageProvider : IRemoteImageProvider
     {
         private readonly IHttpClient _httpClient;
+        private readonly IServerConfigurationManager _config;
 
-        public ManualLastFmImageProvider(IHttpClient httpClient)
+        public ManualLastFmImageProvider(IHttpClient httpClient, IServerConfigurationManager config)
         {
             _httpClient = httpClient;
+            _config = config;
         }
 
         public string Name
@@ -57,6 +61,8 @@ namespace MediaBrowser.Providers.Music
 
             RemoteImageInfo info = null;
 
+            var musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz);
+
             var album = item as MusicAlbum;
             if (album != null)
             {
@@ -64,9 +70,23 @@ namespace MediaBrowser.Providers.Music
             }
 
             var musicArtist = item as MusicArtist;
-            if (musicArtist != null)
+            if (musicArtist != null && !string.IsNullOrEmpty(musicBrainzId))
             {
-                info = GetInfo(musicArtist.LastFmImageUrl, musicArtist.LastFmImageSize);
+                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)
@@ -123,7 +143,7 @@ namespace MediaBrowser.Providers.Music
             {
                 CancellationToken = cancellationToken,
                 Url = url,
-                ResourcePool = LastfmBaseProvider.LastfmResourcePool
+                ResourcePool = LastFmArtistProvider.LastfmResourcePool
             });
         }
     }

+ 59 - 0
MediaBrowser.Providers/ProviderUtils.cs

@@ -104,6 +104,48 @@ namespace MediaBrowser.Providers
                 }
             }
 
+            if (!lockedFields.Contains(MetadataFields.Tags))
+            {
+                var sourceHasTags = source as IHasTags;
+                var targetHasTags = target as IHasTags;
+
+                if (sourceHasTags != null && targetHasTags != null)
+                {
+                    if (replaceData || targetHasTags.Tags.Count == 0)
+                    {
+                        targetHasTags.Tags = sourceHasTags.Tags;
+                    }
+                }
+            }
+
+            if (!lockedFields.Contains(MetadataFields.Keywords))
+            {
+                var sourceHasKeywords = source as IHasKeywords;
+                var targetHasKeywords = target as IHasKeywords;
+
+                if (sourceHasKeywords != null && targetHasKeywords != null)
+                {
+                    if (replaceData || targetHasKeywords.Keywords.Count == 0)
+                    {
+                        targetHasKeywords.Keywords = sourceHasKeywords.Keywords;
+                    }
+                }
+            }
+
+            if (!lockedFields.Contains(MetadataFields.ProductionLocations))
+            {
+                var sourceHasProductionLocations = source as IHasProductionLocations;
+                var targetHasProductionLocations = target as IHasProductionLocations;
+
+                if (sourceHasProductionLocations != null && targetHasProductionLocations != null)
+                {
+                    if (replaceData || targetHasProductionLocations.ProductionLocations.Count == 0)
+                    {
+                        targetHasProductionLocations.ProductionLocations = sourceHasProductionLocations.ProductionLocations;
+                    }
+                }
+            }
+
             if (replaceData || !target.VoteCount.HasValue)
             {
                 target.VoteCount = source.VoteCount;
@@ -120,6 +162,23 @@ namespace MediaBrowser.Providers
                 target.LockedFields = source.LockedFields;
                 target.DontFetchMeta = source.DontFetchMeta;
                 target.DisplayMediaType = source.DisplayMediaType;
+
+                var sourceHasLanguageSettings = source as IHasPreferredMetadataLanguage;
+                var targetHasLanguageSettings = target as IHasPreferredMetadataLanguage;
+
+                if (sourceHasLanguageSettings != null && targetHasLanguageSettings != null)
+                {
+                    targetHasLanguageSettings.PreferredMetadataCountryCode = sourceHasLanguageSettings.PreferredMetadataCountryCode;
+                    targetHasLanguageSettings.PreferredMetadataLanguage = sourceHasLanguageSettings.PreferredMetadataLanguage;
+                }
+
+                var sourceHasDisplayOrder = source as IHasDisplayOrder;
+                var targetHasDisplayOrder = target as IHasDisplayOrder;
+
+                if (sourceHasDisplayOrder != null && targetHasDisplayOrder != null)
+                {
+                    targetHasDisplayOrder.DisplayOrder = sourceHasDisplayOrder.DisplayOrder;
+                }
             }
         }
     }

+ 3 - 2
MediaBrowser.Providers/TV/FanArtSeasonProvider.cs

@@ -15,13 +15,14 @@ using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Net;
 using System.Net;
+using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.TV
 {
     /// <summary>
     /// Class FanArtSeasonProvider
     /// </summary>
-    class FanArtSeasonProvider : FanartBaseProvider
+    class FanArtSeasonProvider : BaseMetadataProvider
     {
         /// <summary>
         /// The _provider manager
@@ -132,7 +133,7 @@ namespace MediaBrowser.Providers.TV
             {
                 try
                 {
-                    await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
+                    await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
                     break;
                 }
                 catch (HttpException ex)

+ 6 - 5
MediaBrowser.Providers/TV/FanArtTVProvider.cs

@@ -18,10 +18,11 @@ using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Net;
 using System.Net;
+using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.TV
 {
-    class FanArtTvProvider : FanartBaseProvider
+    class FanArtTvProvider : BaseMetadataProvider
     {
         protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/series/{0}/{1}/xml/all/1/1";
 
@@ -244,7 +245,7 @@ namespace MediaBrowser.Providers.TV
                 {
                     foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
                     {
-                        await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
+                        await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
                             .ConfigureAwait(false);
 
                         if (item.BackdropImagePaths.Count >= backdropLimit) break;
@@ -259,7 +260,7 @@ namespace MediaBrowser.Providers.TV
             {
                 try
                 {
-                    await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
+                    await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
                     break;
                 }
                 catch (HttpException ex)
@@ -284,7 +285,7 @@ namespace MediaBrowser.Providers.TV
         {
             cancellationToken.ThrowIfCancellationRequested();
 
-            var url = string.Format(FanArtBaseUrl, ApiKey, tvdbId);
+            var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tvdbId);
 
             var xmlPath = GetFanartXmlPath(tvdbId);
 
@@ -293,7 +294,7 @@ namespace MediaBrowser.Providers.TV
             using (var response = await HttpClient.Get(new HttpRequestOptions
             {
                 Url = url,
-                ResourcePool = FanArtResourcePool,
+                ResourcePool = FanartArtistProvider.FanArtResourcePool,
                 CancellationToken = cancellationToken
 
             }).ConfigureAwait(false))

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

@@ -54,7 +54,7 @@ namespace MediaBrowser.Providers.TV
         /// <returns>Task.</returns>
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            if (!_config.Configuration.EnableInternetProviders)
+            if (!_config.Configuration.EnableInternetProviders || !_config.Configuration.EnableFanArtUpdates)
             {
                 progress.Report(100);
                 return;
@@ -108,10 +108,10 @@ namespace MediaBrowser.Providers.TV
             // First get last time
             using (var stream = await _httpClient.Get(new HttpRequestOptions
             {
-                Url = string.Format(UpdatesUrl, FanartBaseProvider.ApiKey, lastUpdateTime),
+                Url = string.Format(UpdatesUrl, FanartArtistProvider.ApiKey, lastUpdateTime),
                 CancellationToken = cancellationToken,
                 EnableHttpCompression = true,
-                ResourcePool = FanartBaseProvider.FanArtResourcePool
+                ResourcePool = FanartArtistProvider.FanArtResourcePool
 
             }).ConfigureAwait(false))
             {

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

@@ -15,6 +15,7 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Xml;
+using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.TV
 {
@@ -268,7 +269,7 @@ namespace MediaBrowser.Providers.TV
             {
                 CancellationToken = cancellationToken,
                 Url = url,
-                ResourcePool = FanartBaseProvider.FanArtResourcePool
+                ResourcePool = FanartArtistProvider.FanArtResourcePool
             });
         }
     }

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

@@ -15,6 +15,7 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Xml;
+using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.TV
 {
@@ -329,7 +330,7 @@ namespace MediaBrowser.Providers.TV
             {
                 CancellationToken = cancellationToken,
                 Url = url,
-                ResourcePool = FanartBaseProvider.FanArtResourcePool
+                ResourcePool = FanartArtistProvider.FanArtResourcePool
             });
         }
     }

+ 1 - 1
MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs

@@ -93,7 +93,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
                     // All other metadata can wait for that.
                     await itemByName.RefreshMetadata(new MetadataRefreshOptions
                     {
-                        ImageRefreshMode = MetadataRefreshMode.None,
+                        ImageRefreshMode = ImageRefreshMode.ValidationOnly,
                         MetadataRefreshMode = MetadataRefreshMode.None
 
                     }, cancellationToken).ConfigureAwait(false);