瀏覽代碼

switch some xmldocuments to xmlreaders

Luke Pulverenti 11 年之前
父節點
當前提交
29cc8c1d4c

+ 106 - 8
MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
@@ -21,6 +22,12 @@ namespace MediaBrowser.Api.DefaultTheme
     {
         [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         public Guid UserId { get; set; }
+
+        [ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string ComedyGenre { get; set; }
+
+        [ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string RomanceGenre { get; set; }
     }
 
     [Route("/MBT/DefaultTheme/Movies", "GET")]
@@ -31,6 +38,19 @@ namespace MediaBrowser.Api.DefaultTheme
 
         [ApiMember(Name = "FamilyRating", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string FamilyRating { get; set; }
+
+        [ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string ComedyGenre { get; set; }
+
+        [ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string RomanceGenre { get; set; }
+    }
+
+    [Route("/MBT/DefaultTheme/Home", "GET")]
+    public class GetHomeView : IReturn<HomeView>
+    {
+        [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public Guid UserId { get; set; }
     }
 
     public class DefaultThemeService : BaseApiService
@@ -51,6 +71,39 @@ namespace MediaBrowser.Api.DefaultTheme
             _localization = localization;
         }
 
+        public object Get(GetHomeView request)
+        {
+            var result = GetHomeView(request).Result;
+
+            return ToOptimizedResult(result);
+        }
+
+        private async Task<HomeView> GetHomeView(GetHomeView request)
+        {
+            var user = _userManager.GetUserById(request.UserId);
+
+            var allItems = user.RootFolder.GetRecursiveChildren(user)
+                .ToList();
+
+            var itemsWithBackdrops = allItems.Where(i => i.BackdropImagePaths.Count > 0).ToList();
+
+            var view = new HomeView();
+
+            var fields = new List<ItemFields>();
+
+            var eligibleSpotlightItems = itemsWithBackdrops
+                .Where(i => i is Game || i is Movie || i is Series || i is MusicArtist);
+
+            var spotlightItemTasks = FilterItemsForBackdropDisplay(eligibleSpotlightItems)
+                .OrderBy(i => Guid.NewGuid())
+                .Take(50)
+                .Select(i => _dtoService.GetBaseItemDto(i, fields, user));
+
+            view.SpotlightItems = await Task.WhenAll(spotlightItemTasks).ConfigureAwait(false);
+
+            return view;
+        }
+
         public object Get(GetTvView request)
         {
             var result = GetTvView(request).Result;
@@ -72,10 +125,9 @@ namespace MediaBrowser.Api.DefaultTheme
 
             var fields = new List<ItemFields>();
 
-            var spotlightItemTasks = seriesWithBackdrops
-                .OrderByDescending(i => GetResolution(i, i.BackdropImagePaths[0]))
-                .Take(60)
+            var spotlightItemTasks = FilterItemsForBackdropDisplay(seriesWithBackdrops)
                 .OrderBy(i => Guid.NewGuid())
+                .Take(50)
                 .Select(i => _dtoService.GetBaseItemDto(i, fields, user));
 
             view.SpotlightItems = await Task.WhenAll(spotlightItemTasks).ConfigureAwait(false);
@@ -88,6 +140,25 @@ namespace MediaBrowser.Api.DefaultTheme
                .Take(3)
                .ToArray();
 
+            var romanceGenres = request.RomanceGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+            var comedyGenres = request.ComedyGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+
+            view.RomanceItems = seriesWithBackdrops
+             .Where(i => i.Genres.Any(romanceGenres.ContainsKey))
+             .OrderBy(i => Guid.NewGuid())
+             .Select(i => GetItemStub(i, ImageType.Backdrop))
+             .Where(i => i != null)
+             .Take(3)
+             .ToArray();
+
+            view.ComedyItems = seriesWithBackdrops
+             .Where(i => i.Genres.Any(comedyGenres.ContainsKey))
+             .OrderBy(i => Guid.NewGuid())
+             .Select(i => GetItemStub(i, ImageType.Backdrop))
+             .Where(i => i != null)
+             .Take(3)
+             .ToArray();
+
             view.ActorItems = await GetActors(series).ConfigureAwait(false);
 
             return view;
@@ -137,10 +208,9 @@ namespace MediaBrowser.Api.DefaultTheme
 
             var fields = new List<ItemFields>();
 
-            var spotlightItemTasks = itemsWithBackdrops
-                .OrderByDescending(i => GetResolution(i, i.BackdropImagePaths[0]))
-                .Take(60)
+            var spotlightItemTasks = FilterItemsForBackdropDisplay(itemsWithBackdrops)
                 .OrderBy(i => Guid.NewGuid())
+                .Take(50)
                 .Select(i => _dtoService.GetBaseItemDto(i, fields, user));
 
             view.SpotlightItems = await Task.WhenAll(spotlightItemTasks).ConfigureAwait(false);
@@ -178,9 +248,10 @@ namespace MediaBrowser.Api.DefaultTheme
              .Take(3)
              .ToArray();
 
-            var romanceGenres = new[] { "romance" }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+            var romanceGenres = request.RomanceGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+            var comedyGenres = request.ComedyGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
 
-            view.RomanticItems = moviesWithBackdrops
+            view.RomanceItems = moviesWithBackdrops
              .Where(i => i.Genres.Any(romanceGenres.ContainsKey))
              .OrderBy(i => Guid.NewGuid())
              .Select(i => GetItemStub(i, ImageType.Backdrop))
@@ -188,6 +259,14 @@ namespace MediaBrowser.Api.DefaultTheme
              .Take(3)
              .ToArray();
 
+            view.ComedyItems = moviesWithBackdrops
+             .Where(i => i.Genres.Any(comedyGenres.ContainsKey))
+             .OrderBy(i => Guid.NewGuid())
+             .Select(i => GetItemStub(i, ImageType.Backdrop))
+             .Where(i => i != null)
+             .Take(3)
+             .ToArray();
+
             view.HDItems = hdMovies
              .Where(i => i.BackdropImagePaths.Count > 0)
              .OrderBy(i => Guid.NewGuid())
@@ -209,6 +288,25 @@ namespace MediaBrowser.Api.DefaultTheme
             return view;
         }
 
+        private IEnumerable<BaseItem> FilterItemsForBackdropDisplay(IEnumerable<BaseItem> items)
+        {
+            var tuples = items
+                .Select(i => new Tuple<BaseItem, double>(i, GetResolution(i, i.BackdropImagePaths[0])))
+                .Where(i => i.Item2 > 0)
+                .ToList();
+
+            var topItems = tuples
+                .Where(i => i.Item2 >= 1920)
+                .ToList();
+
+            if (topItems.Count >= 10)
+            {
+                return topItems.Select(i => i.Item1);
+            }
+
+            return tuples.Select(i => i.Item1);
+        }
+
         private double GetResolution(BaseItem item, string path)
         {
             try

+ 3 - 3
MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs

@@ -1,5 +1,4 @@
-using System.Linq;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
@@ -10,6 +9,7 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using System;
 using System.Globalization;
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -46,7 +46,7 @@ namespace MediaBrowser.Providers.Movies
         {
             get
             {
-                return "8";
+                return "9";
             }
         }
 

+ 145 - 64
MediaBrowser.Providers/TV/RemoteSeasonProvider.cs

@@ -5,10 +5,9 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
 using System;
 using System.IO;
-using System.Net;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Xml;
@@ -77,7 +76,7 @@ namespace MediaBrowser.Providers.TV
                 return ItemUpdateType.ImageUpdate;
             }
         }
-        
+
         /// <summary>
         /// Gets a value indicating whether [refresh on version change].
         /// </summary>
@@ -98,7 +97,7 @@ namespace MediaBrowser.Providers.TV
         {
             get
             {
-                return "1";
+                return "2";
             }
         }
 
@@ -119,7 +118,7 @@ namespace MediaBrowser.Providers.TV
                     return imagesFileInfo.LastWriteTimeUtc;
                 }
             }
-            
+
             return base.CompareDate(item);
         }
 
@@ -138,22 +137,21 @@ namespace MediaBrowser.Providers.TV
 
             var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null;
 
-            if (!string.IsNullOrEmpty(seriesId))
+            var seasonNumber = season.IndexNumber;
+
+            if (!string.IsNullOrEmpty(seriesId) && seasonNumber.HasValue)
             {
                 // Process images
                 var imagesXmlPath = Path.Combine(RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "banners.xml");
 
-                var imagesFileInfo = new FileInfo(imagesXmlPath);
-
-                if (imagesFileInfo.Exists)
+                try
                 {
-                    if (!season.HasImage(ImageType.Primary) || !season.HasImage(ImageType.Banner) || season.BackdropImagePaths.Count == 0)
-                    {
-                        var xmlDoc = new XmlDocument();
-                        xmlDoc.Load(imagesXmlPath);
-
-                        await FetchImages(season, xmlDoc, cancellationToken).ConfigureAwait(false);
-                    }
+                    var fanartData = FetchFanartXmlData(imagesXmlPath, seasonNumber.Value, cancellationToken);
+                    await DownloadImages(item, fanartData, ConfigurationManager.Configuration.MaxBackdrops, cancellationToken).ConfigureAwait(false);
+                }
+                catch (FileNotFoundException)
+                {
+                    // No biggie. Not all series have images
                 }
 
                 SetLastRefreshed(item, DateTime.UtcNow);
@@ -163,86 +161,169 @@ namespace MediaBrowser.Providers.TV
             return false;
         }
 
-        /// <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, XmlDocument images, CancellationToken cancellationToken)
+        private async Task DownloadImages(BaseItem item, FanartXmlData data, int backdropLimit, CancellationToken cancellationToken)
         {
-            var seasonNumber = season.IndexNumber ?? -1;
+            if (!item.HasImage(ImageType.Primary))
+            {
+                var url = data.LanguagePoster ?? data.Poster;
+                if (!string.IsNullOrEmpty(url))
+                {
+                    url = TVUtils.BannerUrl + url;
 
-            if (seasonNumber == -1)
+                    await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
+                      .ConfigureAwait(false);
+                }
+            }
+
+            if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && !item.HasImage(ImageType.Banner))
             {
-                return;
+                var url = data.LanguageBanner ?? data.Banner;
+                if (!string.IsNullOrEmpty(url))
+                {
+                    url = TVUtils.BannerUrl + url;
+
+                    await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
+                      .ConfigureAwait(false);
+                }
             }
 
-            if (!season.HasImage(ImageType.Primary))
+            if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && item.BackdropImagePaths.Count == 0)
             {
-                var n = images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='season'][Season='" + seasonNumber + "'][Language='" + ConfigurationManager.Configuration.PreferredMetadataLanguage + "']") ??
-                        images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='season'][Season='" + seasonNumber + "'][Language='en']");
-                if (n != null)
+                var bdNo = item.BackdropImagePaths.Count;
+
+                foreach (var backdrop in data.Backdrops)
                 {
-                    n = n.SelectSingleNode("./BannerPath");
+                    var url = TVUtils.BannerUrl + backdrop;
 
-                    if (n != null)
-                    {
-                        var url = TVUtils.BannerUrl + n.InnerText;
+                    await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken)
+                      .ConfigureAwait(false);
 
-                        await _providerManager.SaveImage(season, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
-                          .ConfigureAwait(false);
-                    }
+                    bdNo++;
+
+                    if (item.BackdropImagePaths.Count >= backdropLimit) break;
                 }
             }
+        }
+
+        private FanartXmlData FetchFanartXmlData(string bannersXmlPath, int seasonNumber, CancellationToken cancellationToken)
+        {
+            var settings = new XmlReaderSettings
+            {
+                CheckCharacters = false,
+                IgnoreProcessingInstructions = true,
+                IgnoreComments = true,
+                ValidationType = ValidationType.None
+            };
 
-            if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && !season.HasImage(ImageType.Banner))
+            var data = new FanartXmlData();
+
+            using (var streamReader = new StreamReader(bannersXmlPath, Encoding.UTF8))
             {
-                var n = images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='seasonwide'][Season='" + seasonNumber + "'][Language='" + ConfigurationManager.Configuration.PreferredMetadataLanguage + "']") ??
-                        images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='seasonwide'][Season='" + seasonNumber + "'][Language='en']");
-                if (n != null)
+                // Use XmlReader for best performance
+                using (var reader = XmlReader.Create(streamReader, settings))
                 {
-                    n = n.SelectSingleNode("./BannerPath");
-                    if (n != null)
+                    reader.MoveToContent();
+
+                    // Loop through each element
+                    while (reader.Read())
                     {
-                        try
-                        {
-                            var url = TVUtils.BannerUrl + n.InnerText;
+                        cancellationToken.ThrowIfCancellationRequested();
 
-                            await _providerManager.SaveImage(season, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
-                              .ConfigureAwait(false);
-                        }
-                        catch (HttpException ex)
+                        if (reader.NodeType == XmlNodeType.Element)
                         {
-                            Logger.ErrorException("Error downloading season banner for {0}", ex, season.Path);
-
-                            // Sometimes banners will come up not found even though they're reported in tvdb xml
-                            if (ex.StatusCode.HasValue && ex.StatusCode.Value != HttpStatusCode.NotFound)
+                            switch (reader.Name)
                             {
-                                throw;
+                                case "Banner":
+                                    {
+                                        using (var subtree = reader.ReadSubtree())
+                                        {
+                                            FetchInfoFromBannerNode(data, subtree, seasonNumber);
+                                        }
+                                        break;
+                                    }
+                                default:
+                                    reader.Skip();
+                                    break;
                             }
                         }
                     }
                 }
             }
 
-            if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && season.BackdropImagePaths.Count == 0)
+            return data;
+        }
+
+        private void FetchInfoFromBannerNode(FanartXmlData data, XmlReader reader, int seasonNumber)
+        {
+            reader.MoveToContent();
+
+            string bannerType = null;
+            string bannerType2 = null;
+            string url = null;
+            int? bannerSeason = null;
+
+            while (reader.Read())
             {
-                var n = images.SelectSingleNode("//Banner[BannerType='fanart'][Season='" + seasonNumber + "']");
-                if (n != null)
+                if (reader.NodeType == XmlNodeType.Element)
                 {
-                    n = n.SelectSingleNode("./BannerPath");
-                    if (n != null)
+                    switch (reader.Name)
                     {
-                        var url = TVUtils.BannerUrl + n.InnerText;
+                        case "BannerType":
+                            {
+                                bannerType = reader.ReadElementContentAsString() ?? string.Empty;
+                                break;
+                            }
+
+                        case "BannerType2":
+                            {
+                                bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
+                                break;
+                            }
+
+                        case "BannerPath":
+                            {
+                                url = reader.ReadElementContentAsString() ?? string.Empty;
+                                break;
+                            }
 
-                        await _providerManager.SaveImage(season, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, 0, cancellationToken)
-                          .ConfigureAwait(false);
+                        case "Season":
+                            {
+                                var val = reader.ReadElementContentAsString();
+
+                                if (!string.IsNullOrWhiteSpace(val))
+                                {
+                                    bannerSeason = int.Parse(val);
+                                }
+                                break;
+                            }
+
+
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
 
+            if (!string.IsNullOrEmpty(url) && bannerSeason.HasValue && bannerSeason.Value == seasonNumber)
+            {
+                if (string.Equals(bannerType, "season", StringComparison.OrdinalIgnoreCase))
+                {
+                    if (string.Equals(bannerType2, "season", StringComparison.OrdinalIgnoreCase))
+                    {
+                        data.Poster = url;
+                    }
+                    else if (string.Equals(bannerType2, "seasonwide", StringComparison.OrdinalIgnoreCase))
+                    {
+                        data.Banner = url;
                     }
                 }
+                else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
+                {
+                    data.Backdrops.Add(url);
+                }
             }
         }
+
     }
 }

+ 82 - 19
MediaBrowser.Providers/TV/RemoteSeriesProvider.cs

@@ -247,10 +247,7 @@ namespace MediaBrowser.Providers.TV
 
                 if (!series.LockedFields.Contains(MetadataFields.Cast))
                 {
-                    var actorsDoc = new XmlDocument();
-                    actorsDoc.Load(actorsXmlPath);
-
-                    FetchActors(series, actorsDoc, seriesDoc);
+                    FetchActors(series, actorsXmlPath, cancellationToken);
                 }
             }
         }
@@ -384,8 +381,11 @@ namespace MediaBrowser.Providers.TV
                     }
                 }
             }
+
             series.OfficialRating = doc.SafeGetString("//ContentRating");
-            if (!series.LockedFields.Contains(MetadataFields.Genres))
+
+            // Only fill this in if there's no existing genres, because Imdb data from Omdb is preferred
+            if (!series.LockedFields.Contains(MetadataFields.Genres) && (series.Genres.Count == 0 || !string.Equals(ConfigurationManager.Configuration.PreferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase)))
             {
                 string g = doc.SafeGetString("//Genre");
 
@@ -429,31 +429,94 @@ namespace MediaBrowser.Providers.TV
         /// Fetches the actors.
         /// </summary>
         /// <param name="series">The series.</param>
-        /// <param name="actorsDoc">The actors doc.</param>
-        /// <param name="seriesDoc">The seriesDoc.</param>
-        /// <returns>Task.</returns>
-        private void FetchActors(Series series, XmlDocument actorsDoc, XmlDocument seriesDoc)
+        /// <param name="actorsXmlPath">The actors XML path.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        private void FetchActors(Series series, string actorsXmlPath, CancellationToken cancellationToken)
         {
-            var xmlNodeList = actorsDoc.SelectNodes("Actors/Actor");
-
-            if (xmlNodeList != null)
+            var settings = new XmlReaderSettings
             {
-                series.People.Clear();
+                CheckCharacters = false,
+                IgnoreProcessingInstructions = true,
+                IgnoreComments = true,
+                ValidationType = ValidationType.None
+            };
 
-                foreach (XmlNode p in xmlNodeList)
+            using (var streamReader = new StreamReader(actorsXmlPath, Encoding.UTF8))
+            {
+                // Use XmlReader for best performance
+                using (var reader = XmlReader.Create(streamReader, settings))
                 {
-                    string actorName = p.SafeGetString("Name");
-                    string actorRole = p.SafeGetString("Role");
+                    reader.MoveToContent();
 
-                    if (!string.IsNullOrWhiteSpace(actorName))
+                    // Loop through each element
+                    while (reader.Read())
                     {
-                        // Sometimes tvdb actors have leading spaces
-                        series.AddPerson(new PersonInfo { Type = PersonType.Actor, Name = actorName.Trim(), Role = actorRole });
+                        cancellationToken.ThrowIfCancellationRequested();
+
+                        if (reader.NodeType == XmlNodeType.Element)
+                        {
+                            switch (reader.Name)
+                            {
+                                case "Actor":
+                                    {
+                                        using (var subtree = reader.ReadSubtree())
+                                        {
+                                            FetchDataFromActorNode(series, subtree);
+                                        }
+                                        break;
+                                    }
+                                default:
+                                    reader.Skip();
+                                    break;
+                            }
+                        }
                     }
                 }
             }
         }
 
+        /// <summary>
+        /// Fetches the data from actor node.
+        /// </summary>
+        /// <param name="series">The series.</param>
+        /// <param name="reader">The reader.</param>
+        private void FetchDataFromActorNode(Series series, XmlReader reader)
+        {
+            reader.MoveToContent();
+
+            var personInfo = new PersonInfo();
+
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "Name":
+                            {
+                                personInfo.Name = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+                                break;
+                            }
+
+                        case "Role":
+                            {
+                                personInfo.Role = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+                                break;
+                            }
+                        
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
+
+            if (!string.IsNullOrEmpty(personInfo.Name))
+            {
+                series.AddPerson(personInfo);
+            }
+        }
+
         /// <summary>
         /// The us culture
         /// </summary>

+ 95 - 19
MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs

@@ -5,10 +5,10 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
-using MediaBrowser.Providers.Extensions;
 using System;
 using System.IO;
 using System.Linq;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Xml;
@@ -87,38 +87,114 @@ namespace MediaBrowser.Providers.TV
 
             var actorXmlPath = Path.Combine(tvdbPath, "actors.xml");
 
-            var xmlDoc = new XmlDocument();
+            var url = FetchImageUrl(item, actorXmlPath, cancellationToken);
 
-            xmlDoc.Load(actorXmlPath);
-
-            var actorNodes = xmlDoc.SelectNodes("//Actor");
-
-            if (actorNodes == null)
+            if (!string.IsNullOrEmpty(url))
             {
-                return;
-            }
+                url = TVUtils.BannerUrl + url;
 
-            foreach (var actorNode in actorNodes.OfType<XmlNode>())
+                await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool,
+                                               ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
+            }
+        }
+        private string FetchImageUrl(BaseItem item, string actorsXmlPath, CancellationToken cancellationToken)
+        {
+            var settings = new XmlReaderSettings
             {
-                var name = actorNode.SafeGetString("Name");
+                CheckCharacters = false,
+                IgnoreProcessingInstructions = true,
+                IgnoreComments = true,
+                ValidationType = ValidationType.None
+            };
 
-                if (string.Equals(item.Name, name, StringComparison.OrdinalIgnoreCase))
+            using (var streamReader = new StreamReader(actorsXmlPath, Encoding.UTF8))
+            {
+                // Use XmlReader for best performance
+                using (var reader = XmlReader.Create(streamReader, settings))
                 {
-                    var image = actorNode.SafeGetString("Image");
+                    reader.MoveToContent();
 
-                    if (!string.IsNullOrEmpty(image))
+                    // Loop through each element
+                    while (reader.Read())
                     {
-                        var url = TVUtils.BannerUrl + image;
-
-                        await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool,
-                                                       ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
+                        cancellationToken.ThrowIfCancellationRequested();
+
+                        if (reader.NodeType == XmlNodeType.Element)
+                        {
+                            switch (reader.Name)
+                            {
+                                case "Actor":
+                                    {
+                                        using (var subtree = reader.ReadSubtree())
+                                        {
+                                            var url = FetchImageUrlFromActorNode(item, subtree);
+
+                                            if (!string.IsNullOrEmpty(url))
+                                            {
+                                                return url;
+                                            }
+                                        }
+                                        break;
+                                    }
+                                default:
+                                    reader.Skip();
+                                    break;
+                            }
+                        }
                     }
+                }
+            }
 
-                    break;
+            return null;
+        }
+
+        /// <summary>
+        /// Fetches the data from actor node.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="reader">The reader.</param>
+        private string FetchImageUrlFromActorNode(BaseItem item, XmlReader reader)
+        {
+            reader.MoveToContent();
+
+            string name = null;
+            string image = null;
+
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "Name":
+                            {
+                                name = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+                                break;
+                            }
+
+                        case "Image":
+                            {
+                                image = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+                                break;
+                            }
+
+                        default:
+                            reader.Skip();
+                            break;
+                    }
                 }
             }
+
+            if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(image) &&
+                string.Equals(name, item.Name, StringComparison.OrdinalIgnoreCase))
+            {
+                return image;
+            }
+
+            return null;
         }
 
+
         public override MetadataProviderPriority Priority
         {
             get { return MetadataProviderPriority.Third; }

+ 99 - 17
MediaBrowser.Providers/TV/TvdbPrescanTask.cs

@@ -11,7 +11,6 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Xml;
-using MediaBrowser.Providers.Extensions;
 
 namespace MediaBrowser.Providers.TV
 {
@@ -102,11 +101,7 @@ namespace MediaBrowser.Providers.TV
 
                 }).ConfigureAwait(false))
                 {
-                    var doc = new XmlDocument();
-
-                    doc.Load(stream);
-
-                    newUpdateTime = doc.SafeGetString("//Time");
+                    newUpdateTime = GetUpdateTime(stream);
                 }
 
                 await UpdateSeries(existingDirectories, path, progress, cancellationToken).ConfigureAwait(false);
@@ -124,6 +119,51 @@ namespace MediaBrowser.Providers.TV
             progress.Report(100);
         }
 
+        /// <summary>
+        /// Gets the update time.
+        /// </summary>
+        /// <param name="response">The response.</param>
+        /// <returns>System.String.</returns>
+        private string GetUpdateTime(Stream response)
+        {
+            var settings = new XmlReaderSettings
+            {
+                CheckCharacters = false,
+                IgnoreProcessingInstructions = true,
+                IgnoreComments = true,
+                ValidationType = ValidationType.None
+            };
+
+            using (var streamReader = new StreamReader(response, Encoding.UTF8))
+            {
+                // Use XmlReader for best performance
+                using (var reader = XmlReader.Create(streamReader, settings))
+                {
+                    reader.MoveToContent();
+
+                    // Loop through each element
+                    while (reader.Read())
+                    {
+                        if (reader.NodeType == XmlNodeType.Element)
+                        {
+                            switch (reader.Name)
+                            {
+                                case "Time":
+                                    {
+                                        return (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+                                    }
+                                default:
+                                    reader.Skip();
+                                    break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            return null;
+        }
+
         /// <summary>
         /// Gets the series ids to update.
         /// </summary>
@@ -143,23 +183,65 @@ namespace MediaBrowser.Providers.TV
 
             }).ConfigureAwait(false))
             {
-                var doc = new XmlDocument();
+                var data = GetUpdatedSeriesIdList(stream);
 
-                doc.Load(stream);
+                var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
 
-                var newUpdateTime = doc.SafeGetString("//Time");
+                var seriesList = data.Item1
+                    .Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i));
 
-                var seriesNodes = doc.SelectNodes("//Series");
+                return new Tuple<IEnumerable<string>, string>(seriesList, data.Item2);
+            }
+        }
 
-                var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+        private Tuple<List<string>, string> GetUpdatedSeriesIdList(Stream stream)
+        {
+            string updateTime = null;
+            var idList = new List<string>();
 
-                var seriesList = seriesNodes == null ? new string[] { } :
-                    seriesNodes.Cast<XmlNode>()
-                    .Select(i => i.InnerText)
-                    .Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i));
+            var settings = new XmlReaderSettings
+            {
+                CheckCharacters = false,
+                IgnoreProcessingInstructions = true,
+                IgnoreComments = true,
+                ValidationType = ValidationType.None
+            };
 
-                return new Tuple<IEnumerable<string>, string>(seriesList, newUpdateTime);
+            using (var streamReader = new StreamReader(stream, Encoding.UTF8))
+            {
+                // Use XmlReader for best performance
+                using (var reader = XmlReader.Create(streamReader, settings))
+                {
+                    reader.MoveToContent();
+
+                    // Loop through each element
+                    while (reader.Read())
+                    {
+                        if (reader.NodeType == XmlNodeType.Element)
+                        {
+                            switch (reader.Name)
+                            {
+                                case "Time":
+                                    {
+                                        updateTime = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+                                        break;
+                                    }
+                                case "Series":
+                                    {
+                                        var id = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+                                        idList.Add(id);
+                                        break;
+                                    }
+                                default:
+                                    reader.Skip();
+                                    break;
+                            }
+                        }
+                    }
+                }
             }
+
+            return new Tuple<List<string>, string>(idList, updateTime);
         }
 
         /// <summary>
@@ -217,7 +299,7 @@ namespace MediaBrowser.Providers.TV
             {
                 Directory.CreateDirectory(seriesDataPath);
             }
-            
+
             return RemoteSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, cancellationToken);
         }
     }

+ 167 - 52
MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs

@@ -7,8 +7,10 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using System;
+using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Xml;
@@ -86,7 +88,7 @@ namespace MediaBrowser.Providers.TV
                 return ItemUpdateType.ImageUpdate;
             }
         }
-        
+
         /// <summary>
         /// Gets a value indicating whether [refresh on version change].
         /// </summary>
@@ -127,7 +129,7 @@ namespace MediaBrowser.Providers.TV
                     return imagesFileInfo.LastWriteTimeUtc;
                 }
             }
-            
+
             return base.CompareDate(item);
         }
 
@@ -150,16 +152,18 @@ namespace MediaBrowser.Providers.TV
                 // Process images
                 var imagesXmlPath = Path.Combine(RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "banners.xml");
 
-                var imagesFileInfo = new FileInfo(imagesXmlPath);
-
-                if (imagesFileInfo.Exists)
+                if (!series.HasImage(ImageType.Primary) || !series.HasImage(ImageType.Banner) || series.BackdropImagePaths.Count == 0)
                 {
-                    if (!series.HasImage(ImageType.Primary) || !series.HasImage(ImageType.Banner) || series.BackdropImagePaths.Count == 0)
-                    {
-                        var xmlDoc = new XmlDocument();
-                        xmlDoc.Load(imagesXmlPath);
+                    var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
 
-                        await FetchImages(series, xmlDoc, cancellationToken).ConfigureAwait(false);
+                    try
+                    {
+                        var fanartData = FetchFanartXmlData(imagesXmlPath, backdropLimit, cancellationToken);
+                        await DownloadImages(item, fanartData, backdropLimit, cancellationToken).ConfigureAwait(false);
+                    }
+                    catch (FileNotFoundException)
+                    {
+                        // No biggie. Not all series have images
                     }
                 }
 
@@ -179,72 +183,183 @@ namespace MediaBrowser.Providers.TV
 
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
-        /// <summary>
-        /// Fetches the images.
-        /// </summary>
-        /// <param name="series">The series.</param>
-        /// <param name="images">The images.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        private async Task FetchImages(Series series, XmlDocument images, CancellationToken cancellationToken)
+        private async Task DownloadImages(BaseItem item, FanartXmlData data, int backdropLimit, CancellationToken cancellationToken)
         {
-            if (!series.HasImage(ImageType.Primary))
+            if (!item.HasImage(ImageType.Primary))
             {
-                var n = images.SelectSingleNode("//Banner[BannerType='poster']");
-                if (n != null)
+                var url = data.LanguagePoster ?? data.Poster;
+                if (!string.IsNullOrEmpty(url))
                 {
-                    n = n.SelectSingleNode("./BannerPath");
-                    if (n != null)
-                    {
-                        var url = TVUtils.BannerUrl + n.InnerText;
+                    url = TVUtils.BannerUrl + url;
 
-                        await _providerManager.SaveImage(series, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
-                          .ConfigureAwait(false);
-                    }
+                    await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
+                      .ConfigureAwait(false);
                 }
             }
 
-            if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !series.HasImage(ImageType.Banner))
+            if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
             {
-                var n = images.SelectSingleNode("//Banner[BannerType='series']");
-                if (n != null)
+                var url = data.LanguageBanner ?? data.Banner;
+                if (!string.IsNullOrEmpty(url))
                 {
-                    n = n.SelectSingleNode("./BannerPath");
-                    if (n != null)
-                    {
-                        var url = TVUtils.BannerUrl + n.InnerText;
+                    url = TVUtils.BannerUrl + url;
 
-                        await _providerManager.SaveImage(series, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
-                          .ConfigureAwait(false);
-                    }
+                    await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
+                      .ConfigureAwait(false);
+                }
+            }
+
+            if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count == 0)
+            {
+                var bdNo = item.BackdropImagePaths.Count;
+
+                foreach (var backdrop in data.Backdrops)
+                {
+                    var url = TVUtils.BannerUrl + backdrop;
+
+                    await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken)
+                      .ConfigureAwait(false);
+
+                    bdNo++;
+
+                    if (item.BackdropImagePaths.Count >= backdropLimit) break;
                 }
             }
+        }
 
-            if (series.BackdropImagePaths.Count == 0)
+        private FanartXmlData FetchFanartXmlData(string bannersXmlPath, int backdropLimit, CancellationToken cancellationToken)
+        {
+            var settings = new XmlReaderSettings
             {
-                var bdNo = series.BackdropImagePaths.Count;
+                CheckCharacters = false,
+                IgnoreProcessingInstructions = true,
+                IgnoreComments = true,
+                ValidationType = ValidationType.None
+            };
+
+            var data = new FanartXmlData();
 
-                var xmlNodeList = images.SelectNodes("//Banner[BannerType='fanart']");
-                if (xmlNodeList != null)
+            using (var streamReader = new StreamReader(bannersXmlPath, Encoding.UTF8))
+            {
+                // Use XmlReader for best performance
+                using (var reader = XmlReader.Create(streamReader, settings))
                 {
-                    foreach (XmlNode b in xmlNodeList)
+                    reader.MoveToContent();
+
+                    // Loop through each element
+                    while (reader.Read())
                     {
-                        var p = b.SelectSingleNode("./BannerPath");
+                        cancellationToken.ThrowIfCancellationRequested();
 
-                        if (p != null)
+                        if (reader.NodeType == XmlNodeType.Element)
                         {
-                            var url = TVUtils.BannerUrl + p.InnerText;
-
-                            await _providerManager.SaveImage(series, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken)
-                              .ConfigureAwait(false);
-                            
-                            bdNo++;
+                            switch (reader.Name)
+                            {
+                                case "Banner":
+                                    {
+                                        using (var subtree = reader.ReadSubtree())
+                                        {
+                                            FetchInfoFromBannerNode(data, subtree, backdropLimit);
+                                        }
+                                        break;
+                                    }
+                                default:
+                                    reader.Skip();
+                                    break;
+                            }
                         }
+                    }
+                }
+            }
+
+            return data;
+        }
+
+        private void FetchInfoFromBannerNode(FanartXmlData data, XmlReader reader, int backdropLimit)
+        {
+            reader.MoveToContent();
+
+            string type = null;
+            string url = null;
+
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "BannerType":
+                            {
+                                type = reader.ReadElementContentAsString() ?? string.Empty;
+
+                                if (string.Equals(type, "poster", StringComparison.OrdinalIgnoreCase))
+                                {
+                                    // Already got it
+                                    if (!string.IsNullOrEmpty(data.Poster))
+                                    {
+                                        return;
+                                    }
+                                }
+                                else if (string.Equals(type, "series", StringComparison.OrdinalIgnoreCase))
+                                {
+                                    // Already got it
+                                    if (!string.IsNullOrEmpty(data.Banner))
+                                    {
+                                        return;
+                                    }
+                                }
+                                else if (string.Equals(type, "fanart", StringComparison.OrdinalIgnoreCase))
+                                {
+                                    if (data.Backdrops.Count >= backdropLimit)
+                                    {
+                                        return;
+                                    }
+                                }
+                                else
+                                {
+                                    return;
+                                }
 
-                        if (series.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) break;
+                                break;
+                            }
+
+                        case "BannerPath":
+                            {
+                                url = reader.ReadElementContentAsString() ?? string.Empty;
+                                break;
+                            }
+
+                        default:
+                            reader.Skip();
+                            break;
                     }
                 }
             }
+
+            if (!string.IsNullOrEmpty(url))
+            {
+                if (string.Equals(type, "poster", StringComparison.OrdinalIgnoreCase))
+                {
+                    data.Poster = url;
+                }
+                else if (string.Equals(type, "series", StringComparison.OrdinalIgnoreCase))
+                {
+                    data.Banner = url;
+                }
+                else if (string.Equals(type, "fanart", StringComparison.OrdinalIgnoreCase))
+                {
+                    data.Backdrops.Add(url);
+                }
+            }
         }
     }
+
+    internal class FanartXmlData
+    {
+        public string LanguagePoster { get; set; }
+        public string LanguageBanner { get; set; }
+        public string Poster { get; set; }
+        public string Banner { get; set; }
+        public List<string> Backdrops = new List<string>();
+    }
 }