ソースを参照

converted season providers

Luke Pulverenti 11 年 前
コミット
30ebfab8e0
37 ファイル変更493 行追加860 行削除
  1. 8 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  2. 6 3
      MediaBrowser.Controller/Entities/Folder.cs
  3. 0 27
      MediaBrowser.Controller/Entities/User.cs
  4. 1 1
      MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs
  5. 1 1
      MediaBrowser.Providers/Games/GameSystemXmlProvider.cs
  6. 1 1
      MediaBrowser.Providers/Games/GameXmlProvider.cs
  7. 1 1
      MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs
  8. 24 7
      MediaBrowser.Providers/Manager/MetadataService.cs
  9. 6 7
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  10. 1 1
      MediaBrowser.Providers/Music/AlbumXmlProvider.cs
  11. 1 1
      MediaBrowser.Providers/Music/ArtistXmlProvider.cs
  12. 1 1
      MediaBrowser.Providers/People/PersonXmlProvider.cs
  13. 1 1
      MediaBrowser.Providers/Savers/AlbumXmlSaver.cs
  14. 1 1
      MediaBrowser.Providers/Savers/ArtistXmlSaver.cs
  15. 1 1
      MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs
  16. 1 1
      MediaBrowser.Providers/Savers/ChannelXmlSaver.cs
  17. 1 1
      MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs
  18. 1 1
      MediaBrowser.Providers/Savers/FolderXmlSaver.cs
  19. 1 1
      MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs
  20. 1 1
      MediaBrowser.Providers/Savers/GameXmlSaver.cs
  21. 1 1
      MediaBrowser.Providers/Savers/MovieXmlSaver.cs
  22. 1 1
      MediaBrowser.Providers/Savers/PersonXmlSaver.cs
  23. 1 1
      MediaBrowser.Providers/Savers/SeasonXmlSaver.cs
  24. 1 1
      MediaBrowser.Providers/Savers/SeriesXmlSaver.cs
  25. 226 113
      MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
  26. 20 3
      MediaBrowser.Providers/TV/FanArtTVProvider.cs
  27. 0 276
      MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs
  28. 0 83
      MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs
  29. 58 0
      MediaBrowser.Providers/TV/SeasonMetadataService.cs
  30. 0 97
      MediaBrowser.Providers/TV/SeasonProviderFromXml.cs
  31. 63 0
      MediaBrowser.Providers/TV/SeasonXmlProvider.cs
  32. 38 9
      MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs
  33. 0 211
      MediaBrowser.Providers/TV/TvdbSeasonProvider.cs
  34. 20 0
      MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
  35. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  36. 1 1
      Nuget/MediaBrowser.Common.nuspec
  37. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

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

@@ -784,11 +784,19 @@ namespace MediaBrowser.Controller.Entities
                 ResetResolveArgs();
             }
 
+            await BeforeRefreshMetadata(options, cancellationToken).ConfigureAwait(false);
+
             await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false);
 
             return false;
         }
 
+        private readonly Task _cachedTask = Task.FromResult(true);
+        protected virtual Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
+        {
+            return _cachedTask;
+        }
+
         [Obsolete]
         public virtual async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
         {

+ 6 - 3
MediaBrowser.Controller/Entities/Folder.cs

@@ -914,11 +914,14 @@ namespace MediaBrowser.Controller.Entities
             return item;
         }
 
-        public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
+        protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
         {
-            var changed = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
+            if (SupportsShortcutChildren && LocationType == LocationType.FileSystem)
+            {
+                RefreshLinkedChildren();
+            }
 
-            return (SupportsShortcutChildren && LocationType == LocationType.FileSystem && RefreshLinkedChildren()) || changed;
+            return base.BeforeRefreshMetadata(options, cancellationToken);
         }
 
         /// <summary>

+ 0 - 27
MediaBrowser.Controller/Entities/User.cs

@@ -274,33 +274,6 @@ namespace MediaBrowser.Controller.Entities
             serializer.SerializeToFile(Configuration, xmlPath);
         }
 
-        /// <summary>
-        /// Refresh metadata on us by execution our provider chain
-        /// The item will be persisted if a change is made by a provider, or if it's new or changed.
-        /// </summary>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="forceSave">if set to <c>true</c> [is new item].</param>
-        /// <param name="forceRefresh">if set to <c>true</c> [force].</param>
-        /// <returns>true if a provider reports we changed</returns>
-        public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
-        {
-            // Reload this
-            ResetResolveArgs();
-
-            var updateReason = await ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh).ConfigureAwait(false);
-
-            var changed = updateReason.HasValue;
-
-            if (changed || forceSave)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await UserManager.UpdateUser(this).ConfigureAwait(false);
-            }
-
-            return changed;
-        }
-
         /// <summary>
         /// Updates the configuration.
         /// </summary>

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

@@ -51,7 +51,7 @@ namespace MediaBrowser.Providers.BoxSets
 
         public string Name
         {
-            get { return "Media Browser xml"; }
+            get { return "Media Browser Xml"; }
         }
 
         protected override FileInfo GetXmlFile(string path)

+ 1 - 1
MediaBrowser.Providers/Games/GameSystemXmlProvider.cs

@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Games
 
         public string Name
         {
-            get { return "Media Browser xml"; }
+            get { return "Media Browser Xml"; }
         }
 
         protected override FileInfo GetXmlFile(string path)

+ 1 - 1
MediaBrowser.Providers/Games/GameXmlProvider.cs

@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Games
 
         public string Name
         {
-            get { return "Media Browser xml"; }
+            get { return "Media Browser Xml"; }
         }
 
         protected override FileInfo GetXmlFile(string path)

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

@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.LiveTv
 
         public string Name
         {
-            get { return "Media Browser xml"; }
+            get { return "Media Browser Xml"; }
         }
 
         protected override FileInfo GetXmlFile(string path)

+ 24 - 7
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -89,6 +89,8 @@ namespace MediaBrowser.Providers.Manager
             // Next run metadata providers
             if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
             {
+                updateType = updateType | BeforeMetadataRefresh(itemOfType);
+                
                 var providers = GetProviders(item, lastResult.DateLastMetadataRefresh.HasValue, refreshOptions).ToList();
 
                 if (providers.Count > 0)
@@ -100,6 +102,8 @@ namespace MediaBrowser.Providers.Manager
                     refreshResult.SetDateLastMetadataRefresh(DateTime.UtcNow);
                     refreshResult.AddImageProvidersRefreshed(result.Providers);
                 }
+
+                updateType = updateType | AfterMetadataRefresh(itemOfType);
             }
 
             // Next run remote image providers, but only if local image providers didn't throw an exception
@@ -116,8 +120,6 @@ namespace MediaBrowser.Providers.Manager
                     refreshResult.SetDateLastImagesRefresh(DateTime.UtcNow);
                     refreshResult.AddImageProvidersRefreshed(result.Providers);
                 }
-
-                updateType = updateType | AfterMetadataRefresh(itemOfType);
             }
 
             var providersHadChanges = updateType > ItemUpdateType.Unspecified;
@@ -157,6 +159,16 @@ namespace MediaBrowser.Providers.Manager
             return ItemUpdateType.Unspecified;
         }
 
+        /// <summary>
+        /// Befores the metadata refresh.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>ItemUpdateType.</returns>
+        protected virtual ItemUpdateType BeforeMetadataRefresh(TItemType item)
+        {
+            return ItemUpdateType.Unspecified;
+        }
+
         /// <summary>
         /// Gets the providers.
         /// </summary>
@@ -261,12 +273,17 @@ namespace MediaBrowser.Providers.Manager
 
                     if (localItem.HasMetadata)
                     {
-                        MergeData(localItem.Item, temp, new List<MetadataFields>(), !options.ReplaceAllMetadata, true);
-                        refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
+                        if (!string.IsNullOrEmpty(localItem.Item.Name))
+                        {
+                            MergeData(localItem.Item, temp, new List<MetadataFields>(), !options.ReplaceAllMetadata, true);
+                            refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
+
+                            // Only one local provider allowed per item
+                            hasLocalMetadata = true;
+                            break;
+                        }
 
-                        // Only one local provider allowed per item
-                        hasLocalMetadata = true;
-                        break;
+                        Logger.Error("Invalid local metadata found for: " + item.Path);
                     }
                 }
                 catch (OperationCanceledException)

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

@@ -65,6 +65,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="All\LocalImageProvider.cs" />
+    <Compile Include="Books\BookMetadataService.cs" />
     <Compile Include="BoxSets\BoxSetMetadataService.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
@@ -151,26 +152,24 @@
     <Compile Include="TV\EpisodeIndexNumberProvider.cs" />
     <Compile Include="TV\EpisodeProviderFromXml.cs" />
     <Compile Include="TV\EpisodeXmlParser.cs" />
-    <Compile Include="TV\FanArtSeasonProvider.cs" />
     <Compile Include="TV\FanArtTVProvider.cs" />
     <Compile Include="TV\FanArtTvUpdatesPrescanTask.cs" />
-    <Compile Include="TV\ManualFanartSeasonProvider.cs" />
+    <Compile Include="TV\FanartSeasonProvider.cs" />
     <Compile Include="TV\ManualFanartSeriesProvider.cs" />
     <Compile Include="TV\ManualTvdbEpisodeImageProvider.cs" />
     <Compile Include="People\TvdbPersonImageProvider.cs" />
-    <Compile Include="TV\ManualTvdbSeasonImageProvider.cs" />
+    <Compile Include="TV\TvdbSeasonImageProvider.cs" />
     <Compile Include="TV\ManualTvdbSeriesImageProvider.cs" />
-    <Compile Include="TV\SeasonIndexNumberProvider.cs" />
+    <Compile Include="TV\SeasonMetadataService.cs" />
     <Compile Include="TV\TvdbEpisodeProvider.cs" />
-    <Compile Include="TV\TvdbSeasonProvider.cs" />
+    <Compile Include="TV\TvdbSeriesImageProvider.cs" />
     <Compile Include="TV\TvdbSeriesProvider.cs" />
-    <Compile Include="TV\SeasonProviderFromXml.cs" />
+    <Compile Include="TV\SeasonXmlProvider.cs" />
     <Compile Include="TV\SeriesDynamicInfoProvider.cs" />
     <Compile Include="TV\SeriesPostScanTask.cs" />
     <Compile Include="TV\SeriesProviderFromXml.cs" />
     <Compile Include="TV\SeriesXmlParser.cs" />
     <Compile Include="TV\TvdbPrescanTask.cs" />
-    <Compile Include="TV\TvdbSeriesImageProvider.cs" />
     <Compile Include="UserRootFolderNameProvider.cs" />
     <Compile Include="Users\UserMetadataService.cs" />
     <Compile Include="VirtualItemImageValidator.cs" />

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

@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Music
 
         public string Name
         {
-            get { return "Media Browser xml"; }
+            get { return "Media Browser Xml"; }
         }
 
         protected override FileInfo GetXmlFile(string path)

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

@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Music
 
         public string Name
         {
-            get { return "Media Browser xml"; }
+            get { return "Media Browser Xml"; }
         }
 
         protected override FileInfo GetXmlFile(string path)

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

@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.People
 
         public string Name
         {
-            get { return "Media Browser xml"; }
+            get { return "Media Browser Xml"; }
         }
 
         protected override FileInfo GetXmlFile(string path)

+ 1 - 1
MediaBrowser.Providers/Savers/AlbumXmlSaver.cs

@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/ArtistXmlSaver.cs

@@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs

@@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/ChannelXmlSaver.cs

@@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs

@@ -41,7 +41,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/FolderXmlSaver.cs

@@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/GameSystemXmlSaver.cs

@@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/GameXmlSaver.cs

@@ -28,7 +28,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/MovieXmlSaver.cs

@@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/PersonXmlSaver.cs

@@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/SeasonXmlSaver.cs

@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 1 - 1
MediaBrowser.Providers/Savers/SeriesXmlSaver.cs

@@ -24,7 +24,7 @@ namespace MediaBrowser.Providers.Savers
         {
             get
             {
-                return "Media Browser xml";
+                return "Media Browser Xml";
             }
         }
 

+ 226 - 113
MediaBrowser.Providers/TV/FanArtSeasonProvider.cs

@@ -1,190 +1,303 @@
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Music;
 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 MediaBrowser.Providers.Music;
+using System.Xml;
 
 namespace MediaBrowser.Providers.TV
 {
-    /// <summary>
-    /// Class FanArtSeasonProvider
-    /// </summary>
-    class FanArtSeasonProvider : BaseMetadataProvider
+    public class FanartSeasonImageProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
     {
-        /// <summary>
-        /// The _provider manager
-        /// </summary>
-        private readonly IProviderManager _providerManager;
+        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="FanArtSeasonProvider"/> class.
-        /// </summary>
-        /// <param name="logManager">The log manager.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="providerManager">The provider manager.</param>
-        public FanArtSeasonProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
+        public FanartSeasonImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
         {
-            _providerManager = providerManager;
+            _config = config;
+            _httpClient = httpClient;
             _fileSystem = fileSystem;
         }
 
-        public override ItemUpdateType ItemUpdateType
+        public string Name
         {
-            get
-            {
-                return ItemUpdateType.ImageUpdate;
-            }
+            get { return ProviderName; }
         }
-        
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
+
+        public static string ProviderName
+        {
+            get { return "FanArt"; }
+        }
+
+        public bool Supports(IHasImages item)
         {
             return item is Season;
         }
 
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
+        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
         {
-            get { return MetadataProviderPriority.Third; }
+            return new List<ImageType>
+            {
+                ImageType.Backdrop, 
+                ImageType.Thumb
+            };
         }
 
-        protected override DateTime CompareDate(BaseItem item)
+        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 list = new List<RemoteImageInfo>();
+
             var season = (Season)item;
-            var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null;
+            var series = season.Series;
 
-            if (!string.IsNullOrEmpty(seriesId))
+            if (series != null)
             {
-                // Process images
-                var imagesXmlPath = FanArtTvProvider.Current.GetFanartXmlPath(seriesId);
+                var id = series.GetProviderId(MetadataProviders.Tvdb);
 
-                var imagesFileInfo = new FileInfo(imagesXmlPath);
-
-                if (imagesFileInfo.Exists)
+                if (!string.IsNullOrEmpty(id) && season.IndexNumber.HasValue)
                 {
-                    return _fileSystem.GetLastWriteTimeUtc(imagesFileInfo);
+                    await FanArtTvProvider.Current.EnsureSeriesXml(id, cancellationToken).ConfigureAwait(false);
+
+                    var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id);
+
+                    try
+                    {
+                        AddImages(list, season.IndexNumber.Value, xmlPath, cancellationToken);
+                    }
+                    catch (FileNotFoundException)
+                    {
+                        // No biggie. Don't blow up
+                    }
                 }
             }
 
-            return base.CompareDate(item);
+            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>
-        /// 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)
+        private void AddImages(List<RemoteImageInfo> list, int seasonNumber, string xmlPath, CancellationToken cancellationToken)
         {
-            cancellationToken.ThrowIfCancellationRequested();
+            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();
 
-            var season = (Season) item;
+                    // Loop through each element
+                    while (reader.Read())
+                    {
+                        cancellationToken.ThrowIfCancellationRequested();
 
-            // Process images
-            var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartSeasonImageProvider.ProviderName).ConfigureAwait(false);
-            await FetchImages(season, images.ToList(), cancellationToken).ConfigureAwait(false);
+                        if (reader.NodeType == XmlNodeType.Element)
+                        {
+                            switch (reader.Name)
+                            {
+                                case "series":
+                                    {
+                                        using (var subReader = reader.ReadSubtree())
+                                        {
+                                            AddImages(list, subReader, seasonNumber, cancellationToken);
+                                        }
+                                        break;
+                                    }
 
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
+                                default:
+                                    reader.Skip();
+                                    break;
+                            }
+                        }
+                    }
+                }
+            }
         }
 
-        /// <summary>
-        /// Fetches the images.
-        /// </summary>
-        /// <param name="season">The season.</param>
-        /// <param name="images">The images.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        private async Task FetchImages(Season season, List<RemoteImageInfo> images, CancellationToken cancellationToken)
+        private void AddImages(List<RemoteImageInfo> list, XmlReader reader, int seasonNumber, CancellationToken cancellationToken)
         {
-            var options = ConfigurationManager.Configuration.GetMetadataOptions("Season") ?? new MetadataOptions();
+            reader.MoveToContent();
 
-            if (options.IsEnabled(ImageType.Thumb) && !season.HasImage(ImageType.Thumb) && !season.LockedFields.Contains(MetadataFields.Images))
+            while (reader.Read())
             {
-                await SaveImage(season, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "seasonthumbs":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    PopulateImageCategory(list, subReader, cancellationToken, ImageType.Thumb, 500, 281, seasonNumber);
+                                }
+                                break;
+                            }
+                        case "showbackgrounds":
+                            {
+                                using (var subReader = reader.ReadSubtree())
+                                {
+                                    PopulateImageCategory(list, subReader, cancellationToken, ImageType.Backdrop, 1920, 1080, seasonNumber);
+                                }
+                                break;
+                            }
+                        default:
+                            {
+                                using (reader.ReadSubtree())
+                                {
+                                }
+                                break;
+                            }
+                    }
+                }
             }
         }
 
-        private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
+        private void PopulateImageCategory(List<RemoteImageInfo> list, XmlReader reader, CancellationToken cancellationToken, ImageType type, int width, int height, int seasonNumber)
         {
-            foreach (var image in images.Where(i => i.Type == type))
+            reader.MoveToContent();
+
+            while (reader.Read())
             {
-                try
-                {
-                    await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
-                    break;
-                }
-                catch (HttpException ex)
+                cancellationToken.ThrowIfCancellationRequested();
+
+                if (reader.NodeType == XmlNodeType.Element)
                 {
-                    // Sometimes fanart has bad url's in their xml
-                    if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
+                    switch (reader.Name)
                     {
-                        continue;
+                        case "seasonthumb":
+                        case "showbackground":
+                            {
+                                var url = reader.GetAttribute("url");
+                                var season = reader.GetAttribute("season");
+
+                                int imageSeasonNumber;
+
+                                if (!string.IsNullOrEmpty(url) &&
+                                    !string.IsNullOrEmpty(season) &&
+                                    int.TryParse(season, NumberStyles.Any, _usCulture, out imageSeasonNumber) &&
+                                    seasonNumber == imageSeasonNumber)
+                                {
+                                    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);
+                                }
+
+                                break;
+                            }
+                        default:
+                            reader.Skip();
+                            break;
                     }
-                    break;
                 }
             }
         }
 
-        /// <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
+        public int Order
         {
-            get
-            {
-                return true;
-            }
+            get { return 1; }
         }
 
-        /// <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 Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            get
+            return _httpClient.GetResponse(new HttpRequestOptions
             {
-                return true;
-            }
+                CancellationToken = cancellationToken,
+                Url = url,
+                ResourcePool = FanartArtistProvider.FanArtResourcePool
+            });
         }
 
-        /// <summary>
-        /// Gets the provider version.
-        /// </summary>
-        /// <value>The provider version.</value>
-        protected override string ProviderVersion
+        public bool HasChanged(IHasMetadata item, DateTime date)
         {
-            get
+            var season = (Season)item;
+            var series = season.Series;
+
+            if (series == null)
             {
-                return "3";
+                return false;
             }
+
+            var tvdbId = series.GetProviderId(MetadataProviders.Tvdb);
+
+            if (!String.IsNullOrEmpty(tvdbId))
+            {
+                // Process images
+                var imagesXmlPath = FanArtTvProvider.Current.GetFanartXmlPath(tvdbId);
+
+                var fileInfo = new FileInfo(imagesXmlPath);
+
+                return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date;
+            }
+
+            return false;
         }
     }
 }

+ 20 - 3
MediaBrowser.Providers/TV/FanArtTVProvider.cs

@@ -72,7 +72,7 @@ namespace MediaBrowser.Providers.TV
                 return ItemUpdateType.ImageUpdate;
             }
         }
-        
+
         /// <summary>
         /// Needses the refresh internal.
         /// </summary>
@@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.TV
             var dataPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, tvdbId);
             return Path.Combine(dataPath, "fanart.xml");
         }
-        
+
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
         public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
@@ -199,7 +199,7 @@ namespace MediaBrowser.Providers.TV
         private async Task FetchFromXml(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
         {
             var options = ConfigurationManager.Configuration.GetMetadataOptions("Series") ?? new MetadataOptions();
-            
+
             if (!item.LockedFields.Contains(MetadataFields.Images))
             {
                 cancellationToken.ThrowIfCancellationRequested();
@@ -278,6 +278,23 @@ namespace MediaBrowser.Providers.TV
             }
         }
 
+        internal Task EnsureSeriesXml(string tvdbId, CancellationToken cancellationToken)
+        {
+            var xmlPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, tvdbId);
+
+            var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath);
+
+            if (fileInfo.Exists)
+            {
+                if (ConfigurationManager.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
+                {
+                    return Task.FromResult(true);
+                }
+            }
+
+            return DownloadSeriesXml(tvdbId, cancellationToken);
+        }
+
         /// <summary>
         /// Downloads the series XML.
         /// </summary>

+ 0 - 276
MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs

@@ -1,276 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-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;
-using MediaBrowser.Providers.Music;
-
-namespace MediaBrowser.Providers.TV
-{
-    public class ManualFanartSeasonImageProvider : IRemoteImageProvider, IHasOrder
-    {
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-        private readonly IServerConfigurationManager _config;
-        private readonly IHttpClient _httpClient;
-
-        public ManualFanartSeasonImageProvider(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 Season;
-        }
-
-        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
-        {
-            return new List<ImageType>
-            {
-                ImageType.Backdrop, 
-                ImageType.Thumb
-            };
-        }
-
-        public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
-        {
-            var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
-
-            return images.Where(i => i.Type == imageType);
-        }
-
-        public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
-        {
-            var list = new List<RemoteImageInfo>();
-
-            var season = (Season)item;
-            var series = season.Series;
-
-            if (series != null)
-            {
-                var id = series.GetProviderId(MetadataProviders.Tvdb);
-
-                if (!string.IsNullOrEmpty(id) && season.IndexNumber.HasValue)
-                {
-                    var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id);
-
-                    try
-                    {
-                        AddImages(list, season.IndexNumber.Value, xmlPath, cancellationToken);
-                    }
-                    catch (FileNotFoundException)
-                    {
-                        // No biggie. Don't blow up
-                    }
-                }
-            }
-
-            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);
-        }
-
-        private void AddImages(List<RemoteImageInfo> list, int seasonNumber, 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 "series":
-                                    {
-                                        using (var subReader = reader.ReadSubtree())
-                                        {
-                                            AddImages(list, subReader, seasonNumber, cancellationToken);
-                                        }
-                                        break;
-                                    }
-
-                                default:
-                                    reader.Skip();
-                                    break;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        private void AddImages(List<RemoteImageInfo> list, XmlReader reader, int seasonNumber, CancellationToken cancellationToken)
-        {
-            reader.MoveToContent();
-
-            while (reader.Read())
-            {
-                if (reader.NodeType == XmlNodeType.Element)
-                {
-                    switch (reader.Name)
-                    {
-                        case "seasonthumbs":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    PopulateImageCategory(list, subReader, cancellationToken, ImageType.Thumb, 500, 281, seasonNumber);
-                                }
-                                break;
-                            }
-                        case "showbackgrounds":
-                            {
-                                using (var subReader = reader.ReadSubtree())
-                                {
-                                    PopulateImageCategory(list, subReader, cancellationToken, ImageType.Backdrop, 1920, 1080, seasonNumber);
-                                }
-                                break;
-                            }
-                        default:
-                            {
-                                using (reader.ReadSubtree())
-                                {
-                                }
-                                break;
-                            }
-                    }
-                }
-            }
-        }
-
-        private void PopulateImageCategory(List<RemoteImageInfo> list, XmlReader reader, CancellationToken cancellationToken, ImageType type, int width, int height, int seasonNumber)
-        {
-            reader.MoveToContent();
-
-            while (reader.Read())
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                if (reader.NodeType == XmlNodeType.Element)
-                {
-                    switch (reader.Name)
-                    {
-                        case "seasonthumb":
-                        case "showbackground":
-                            {
-                                var url = reader.GetAttribute("url");
-                                var season = reader.GetAttribute("season");
-
-                                int imageSeasonNumber;
-
-                                if (!string.IsNullOrEmpty(url) &&
-                                    !string.IsNullOrEmpty(season) &&
-                                    int.TryParse(season, NumberStyles.Any, _usCulture, out imageSeasonNumber) &&
-                                    seasonNumber == imageSeasonNumber)
-                                {
-                                    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);
-                                }
-
-                                break;
-                            }
-                        default:
-                            reader.Skip();
-                            break;
-                    }
-                }
-            }
-        }
-
-        public int Order
-        {
-            get { return 1; }
-        }
-
-        public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
-        {
-            return _httpClient.GetResponse(new HttpRequestOptions
-            {
-                CancellationToken = cancellationToken,
-                Url = url,
-                ResourcePool = FanartArtistProvider.FanArtResourcePool
-            });
-        }
-    }
-}

+ 0 - 83
MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs

@@ -1,83 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.TV
-{
-    class SeasonIndexNumberProvider : BaseMetadataProvider
-    {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class.
-        /// </summary>
-        /// <param name="logManager">The log manager.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        public SeasonIndexNumberProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
-            : base(logManager, configurationManager)
-        {
-        }
-
-        protected override bool RefreshOnVersionChange
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        protected override string ProviderVersion
-        {
-            get
-            {
-                return "2";
-            }
-        }
-
-        /// <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)
-        {
-            if (item is Season)
-            {
-                var locationType = item.LocationType;
-                return locationType != LocationType.Virtual && locationType != LocationType.Remote;
-            }
-            return false;
-        }
-
-        /// <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="providerInfo">The provider information.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            item.IndexNumber = TVUtils.GetSeasonNumberFromPath(item.Path);
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-
-            return TrueTaskResult;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.First; }
-        }
-    }
-}

+ 58 - 0
MediaBrowser.Providers/TV/SeasonMetadataService.cs

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

+ 0 - 97
MediaBrowser.Providers/TV/SeasonProviderFromXml.cs

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

+ 63 - 0
MediaBrowser.Providers/TV/SeasonXmlProvider.cs

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

+ 38 - 9
MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs → MediaBrowser.Providers/TV/TvdbSeasonImageProvider.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.TV;
@@ -19,16 +20,18 @@ using System.Xml;
 
 namespace MediaBrowser.Providers.TV
 {
-    public class ManualTvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
+    public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
     {
         private readonly IServerConfigurationManager _config;
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly IHttpClient _httpClient;
+        private readonly IFileSystem _fileSystem;
 
-        public ManualTvdbSeasonImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
+        public TvdbSeasonImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
         {
             _config = config;
             _httpClient = httpClient;
+            _fileSystem = fileSystem;
         }
 
         public string Name
@@ -63,14 +66,17 @@ namespace MediaBrowser.Providers.TV
             return images.Where(i => i.Type == imageType);
         }
 
-        public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
+        public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
         {
             var season = (Season)item;
+            var series = season.Series;
 
-            var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null;
+            var seriesId = series != null ? series.GetProviderId(MetadataProviders.Tvdb) : null;
 
             if (!string.IsNullOrEmpty(seriesId) && season.IndexNumber.HasValue)
             {
+                await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesId, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
+
                 // Process images
                 var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);
 
@@ -78,9 +84,7 @@ namespace MediaBrowser.Providers.TV
 
                 try
                 {
-                    var result = GetImages(path, item.GetPreferredMetadataLanguage(), season.IndexNumber.Value, cancellationToken);
-
-                    return Task.FromResult(result);
+                    return GetImages(path, item.GetPreferredMetadataLanguage(), season.IndexNumber.Value, cancellationToken);
                 }
                 catch (FileNotFoundException)
                 {
@@ -88,7 +92,7 @@ namespace MediaBrowser.Providers.TV
                 }
             }
 
-            return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { });
+            return new RemoteImageInfo[] { };
         }
 
         private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, int seasonNumber, CancellationToken cancellationToken)
@@ -335,5 +339,30 @@ namespace MediaBrowser.Providers.TV
                 ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
             });
         }
+
+        public bool HasChanged(IHasMetadata item, DateTime date)
+        {
+            var season = (Season)item;
+            var series = season.Series;
+
+            if (series == null)
+            {
+                return false;
+            }
+
+            var tvdbId = series.GetProviderId(MetadataProviders.Tvdb);
+
+            if (!String.IsNullOrEmpty(tvdbId))
+            {
+                // Process images
+                var imagesXmlPath = Path.Combine(TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, tvdbId), "banners.xml");
+
+                var fileInfo = new FileInfo(imagesXmlPath);
+
+                return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date;
+            }
+
+            return false;
+        }
     }
 }

+ 0 - 211
MediaBrowser.Providers/TV/TvdbSeasonProvider.cs

@@ -1,211 +0,0 @@
-using System.Net;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Providers;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.TV
-{
-    /// <summary>
-    /// Class RemoteSeasonProvider
-    /// </summary>
-    class TvdbSeasonProvider : BaseMetadataProvider
-    {
-        /// <summary>
-        /// The _provider manager
-        /// </summary>
-        private readonly IProviderManager _providerManager;
-        private readonly IFileSystem _fileSystem;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="TvdbSeasonProvider"/> class.
-        /// </summary>
-        /// <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 TvdbSeasonProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
-        {
-            _providerManager = providerManager;
-            _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 Season;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            // Run after fanart
-            get { return MetadataProviderPriority.Fourth; }
-        }
-
-        /// <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;
-            }
-        }
-
-        public override ItemUpdateType ItemUpdateType
-        {
-            get
-            {
-                return ItemUpdateType.ImageUpdate;
-            }
-        }
-
-        /// <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
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Gets the provider version.
-        /// </summary>
-        /// <value>The provider version.</value>
-        protected override string ProviderVersion
-        {
-            get
-            {
-                return "2";
-            }
-        }
-
-        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var season = (Season)item;
-            var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null;
-
-            if (!string.IsNullOrEmpty(seriesId))
-            {
-                // Process images
-                var imagesXmlPath = Path.Combine(TvdbSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "banners.xml");
-
-                var imagesFileInfo = new FileInfo(imagesXmlPath);
-
-                if (imagesFileInfo.Exists)
-                {
-                    return _fileSystem.GetLastWriteTimeUtc(imagesFileInfo) > providerInfo.LastRefreshed;
-                }
-            }
-            return false;
-        }
-
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count > 0)
-            {
-                return false;
-            }
-            return base.NeedsRefreshInternal(item, providerInfo);
-        }
-
-        /// <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 images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualTvdbSeasonImageProvider.ProviderName).ConfigureAwait(false);
-
-            await DownloadImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
-        }
-
-        private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
-        {
-            var options = ConfigurationManager.Configuration.GetMetadataOptions("Season") ?? new MetadataOptions();
-            var backdropLimit = options.GetLimit(ImageType.Backdrop);
-
-            if (!item.LockedFields.Contains(MetadataFields.Images))
-            {
-                if (!item.HasImage(ImageType.Primary))
-                {
-                    await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
-                }
-
-                if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
-                {
-                    await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
-                }
-            }
-
-            if (options.IsEnabled(ImageType.Backdrop) && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
-            {
-                foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop))
-                {
-                    var url = backdrop.Url;
-
-                    await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, null, cancellationToken).ConfigureAwait(false);
-
-                    if (item.BackdropImagePaths.Count >= backdropLimit) break;
-                }
-            }
-        }
-
-        private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
-        {
-            foreach (var image in images.Where(i => i.Type == type))
-            {
-                try
-                {
-                    await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, 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;
-                }
-            }
-        }
-    }
-}

+ 20 - 0
MediaBrowser.Providers/TV/TvdbSeriesProvider.cs

@@ -324,6 +324,26 @@ namespace MediaBrowser.Providers.TV
             await ExtractEpisodes(seriesDataPath, Path.Combine(seriesDataPath, preferredMetadataLanguage + ".xml"), lastTvDbUpdateTime).ConfigureAwait(false);
         }
 
+        internal async Task EnsureSeriesInfo(string seriesId, string preferredMetadataLanguage, CancellationToken cancellationToken)
+        {
+            var seriesDataPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId);
+
+            Directory.CreateDirectory(seriesDataPath);
+
+            var files = Directory.EnumerateFiles(seriesDataPath, "*.xml", SearchOption.TopDirectoryOnly)
+                .Select(Path.GetFileName)
+                .ToList();
+
+            var seriesXmlFilename = preferredMetadataLanguage + ".xml";
+
+            // Only download if not already there
+            // The prescan task will take care of updates so we don't need to re-download here
+            if (!files.Contains("banners.xml", StringComparer.OrdinalIgnoreCase) || !files.Contains("actors.xml", StringComparer.OrdinalIgnoreCase) || !files.Contains(seriesXmlFilename, StringComparer.OrdinalIgnoreCase))
+            {
+                await DownloadSeriesZip(seriesId, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
+            }
+        }
+
         private void DeleteXmlFiles(string path)
         {
             try

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

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

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

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

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

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