diff --git a/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs b/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs
index b79e50e20..beacd962c 100644
--- a/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Configuration;
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -8,7 +9,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
-using MediaBrowser.Providers.Extensions;
using System;
using System.Globalization;
using System.IO;
@@ -18,7 +18,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
-using System.Xml.Linq;
namespace MediaBrowser.Providers.TV
{
@@ -240,10 +239,7 @@ namespace MediaBrowser.Providers.TV
var seriesXmlPath = Path.Combine(seriesDataPath, seriesXmlFilename);
var actorsXmlPath = Path.Combine(seriesDataPath, "actors.xml");
- var seriesDoc = new XmlDocument();
- seriesDoc.Load(seriesXmlPath);
-
- FetchMainInfo(series, seriesDoc);
+ FetchSeriesInfo(series, seriesXmlPath, cancellationToken);
if (!series.LockedFields.Contains(MetadataFields.Cast))
{
@@ -317,114 +313,365 @@ namespace MediaBrowser.Providers.TV
return dataPath;
}
- ///
- /// Fetches the main info.
- ///
- /// The series.
- /// The doc.
- private void FetchMainInfo(Series series, XmlDocument doc)
+ private void FetchSeriesInfo(Series item, string seriesXmlPath, CancellationToken cancellationToken)
{
- if (!series.LockedFields.Contains(MetadataFields.Name))
+ var settings = new XmlReaderSettings
{
- series.Name = doc.SafeGetString("//SeriesName");
- }
- if (!series.LockedFields.Contains(MetadataFields.Overview))
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true,
+ ValidationType = ValidationType.None
+ };
+
+ var episiodeAirDates = new List();
+
+ using (var streamReader = new StreamReader(seriesXmlPath, Encoding.UTF8))
{
- series.Overview = doc.SafeGetString("//Overview");
- }
-
- var imdbId = doc.SafeGetString("//IMDB_ID");
-
- if (!string.IsNullOrWhiteSpace(imdbId))
- {
- series.SetProviderId(MetadataProviders.Imdb, imdbId);
- }
-
- var zap2ItId = doc.SafeGetString("//zap2it_id");
-
- if (!string.IsNullOrWhiteSpace(zap2ItId))
- {
- series.SetProviderId(MetadataProviders.Zap2It, zap2ItId);
- }
-
- // Only fill this if it doesn't already have a value, since we get it from imdb which has better data
- if (!series.CommunityRating.HasValue || string.IsNullOrWhiteSpace(series.GetProviderId(MetadataProviders.Imdb)))
- {
- series.CommunityRating = doc.SafeGetSingle("//Rating", 0, 10);
- }
-
- series.AirDays = TVUtils.GetAirDays(doc.SafeGetString("//Airs_DayOfWeek"));
- series.AirTime = doc.SafeGetString("//Airs_Time");
- SeriesStatus seriesStatus;
- if(Enum.TryParse(doc.SafeGetString("//Status"), true, out seriesStatus))
- series.Status = seriesStatus;
- series.PremiereDate = doc.SafeGetDateTime("//FirstAired");
- if (series.PremiereDate.HasValue)
- series.ProductionYear = series.PremiereDate.Value.Year;
-
- if (!series.LockedFields.Contains(MetadataFields.Runtime))
- {
- series.RunTimeTicks = TimeSpan.FromMinutes(doc.SafeGetInt32("//Runtime")).Ticks;
- }
-
- if (!series.LockedFields.Contains(MetadataFields.Studios))
- {
- string s = doc.SafeGetString("//Network");
-
- if (!string.IsNullOrWhiteSpace(s))
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader, settings))
{
- series.Studios.Clear();
+ reader.MoveToContent();
- foreach (var studio in s.Trim().Split('|'))
+ // Loop through each element
+ while (reader.Read())
{
- series.AddStudio(studio);
- }
- }
- }
+ cancellationToken.ThrowIfCancellationRequested();
- series.OfficialRating = doc.SafeGetString("//ContentRating");
-
- // 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");
-
- if (g != null)
- {
- string[] genres = g.Trim('|').Split('|');
- if (g.Length > 0)
- {
- series.Genres.Clear();
-
- foreach (var genre in genres)
+ if (reader.NodeType == XmlNodeType.Element)
{
- series.AddGenre(genre);
+ switch (reader.Name)
+ {
+ case "Series":
+ {
+ using (var subtree = reader.ReadSubtree())
+ {
+ FetchDataFromSeriesNode(item, subtree, cancellationToken);
+ }
+ break;
+ }
+
+ case "Episode":
+ {
+ using (var subtree = reader.ReadSubtree())
+ {
+ var date = GetFirstAiredDateFromEpisodeNode(subtree, cancellationToken);
+
+ if (date.HasValue)
+ {
+ episiodeAirDates.Add(date.Value);
+ }
+ }
+ break;
+ }
+
+ default:
+ reader.Skip();
+ break;
+ }
}
}
}
}
- if (series.Status == SeriesStatus.Ended) {
-
- var document = XDocument.Load(new XmlNodeReader(doc));
- var dates = document.Descendants("Episode").Where(x => {
- var seasonNumber = x.Element("SeasonNumber");
- var firstAired = x.Element("FirstAired");
- return firstAired != null && seasonNumber != null && (!string.IsNullOrEmpty(seasonNumber.Value) && seasonNumber.Value != "0") && !string.IsNullOrEmpty(firstAired.Value);
- }).Select(x => {
- DateTime? date = null;
- DateTime tempDate;
- var firstAired = x.Element("FirstAired");
- if (firstAired != null && DateTime.TryParse(firstAired.Value, out tempDate))
- {
- date = tempDate;
- }
- return date;
- }).ToList();
- if(dates.Any(x=>x.HasValue))
- series.EndDate = dates.Where(x => x.HasValue).Max();
+
+ if (item.Status.HasValue && item.Status.Value == SeriesStatus.Ended && episiodeAirDates.Count > 0)
+ {
+ item.EndDate = episiodeAirDates.Max();
}
}
+ private void FetchDataFromSeriesNode(Series item, XmlReader reader, CancellationToken cancellationToken)
+ {
+ reader.MoveToContent();
+
+ // Loop through each element
+ while (reader.Read())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "SeriesName":
+ {
+ if (!item.LockedFields.Contains(MetadataFields.Name))
+ {
+ item.Name = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ }
+ break;
+ }
+
+ case "Overview":
+ {
+ if (!item.LockedFields.Contains(MetadataFields.Overview))
+ {
+ item.Overview = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ }
+ break;
+ }
+
+ case "Airs_DayOfWeek":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.AirDays = TVUtils.GetAirDays(val);
+ }
+ break;
+ }
+
+ case "Airs_Time":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.AirTime = val;
+ }
+ break;
+ }
+
+ case "ContentRating":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.OfficialRating = val;
+ }
+ break;
+ }
+
+ case "Rating":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ // Only fill this if it doesn't already have a value, since we get it from imdb which has better data
+ if (!item.CommunityRating.HasValue || string.IsNullOrWhiteSpace(item.GetProviderId(MetadataProviders.Imdb)))
+ {
+ float rval;
+
+ // float.TryParse is local aware, so it can be probamatic, force us culture
+ if (float.TryParse(val, NumberStyles.AllowDecimalPoint, UsCulture, out rval))
+ {
+ item.CommunityRating = rval;
+ }
+ }
+ }
+ break;
+ }
+
+ case "IMDB_ID":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.SetProviderId(MetadataProviders.Imdb, val);
+ }
+
+ break;
+ }
+
+ case "zap2it_id":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.SetProviderId(MetadataProviders.Zap2It, val);
+ }
+
+ break;
+ }
+
+ case "Status":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ SeriesStatus seriesStatus;
+
+ if (Enum.TryParse(val, true, out seriesStatus))
+ item.Status = seriesStatus;
+ }
+
+ break;
+ }
+
+ case "FirstAired":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ DateTime date;
+ if (DateTime.TryParse(val, out date))
+ {
+ date = date.ToUniversalTime();
+
+ item.PremiereDate = date;
+ item.ProductionYear = date.Year;
+ }
+ }
+
+ break;
+ }
+
+ case "Runtime":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val) && !item.LockedFields.Contains(MetadataFields.Runtime))
+ {
+ int rval;
+
+ // int.TryParse is local aware, so it can be probamatic, force us culture
+ if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval))
+ {
+ item.RunTimeTicks = TimeSpan.FromMinutes(rval).Ticks;
+ }
+ }
+
+ break;
+ }
+
+ case "Genre":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ // Only fill this in if there's no existing genres, because Imdb data from Omdb is preferred
+ if (!item.LockedFields.Contains(MetadataFields.Genres) && (item.Genres.Count == 0 || !string.Equals(ConfigurationManager.Configuration.PreferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase)))
+ {
+ var vals = val
+ .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(i => i.Trim())
+ .Where(i => !string.IsNullOrWhiteSpace(i))
+ .ToArray();
+
+ if (vals.Length > 0)
+ {
+ item.Genres.Clear();
+
+ foreach (var genre in vals)
+ {
+ item.AddGenre(genre);
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ case "Network":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ if (!item.LockedFields.Contains(MetadataFields.Studios))
+ {
+ var vals = val
+ .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(i => i.Trim())
+ .Where(i => !string.IsNullOrWhiteSpace(i))
+ .ToArray();
+
+ if (vals.Length > 0)
+ {
+ item.Studios.Clear();
+
+ foreach (var genre in vals)
+ {
+ item.AddStudio(genre);
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ }
+
+ private DateTime? GetFirstAiredDateFromEpisodeNode(XmlReader reader, CancellationToken cancellationToken)
+ {
+ DateTime? airDate = null;
+ int? seasonNumber = null;
+
+ reader.MoveToContent();
+
+ // Loop through each element
+ while (reader.Read())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "FirstAired":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ DateTime date;
+ if (DateTime.TryParse(val, out date))
+ {
+ airDate = date.ToUniversalTime();
+ }
+ }
+
+ break;
+ }
+
+ case "SeasonNumber":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ int rval;
+
+ // int.TryParse is local aware, so it can be probamatic, force us culture
+ if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval))
+ {
+ seasonNumber = rval;
+ }
+ }
+
+ break;
+ }
+
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
+
+ if (seasonNumber.HasValue && seasonNumber.Value != 0)
+ {
+ return airDate;
+ }
+
+ return null;
+ }
+
///
/// Fetches the actors.
///
@@ -503,7 +750,7 @@ namespace MediaBrowser.Providers.TV
personInfo.Role = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
break;
}
-
+
default:
reader.Skip();
break;