diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index aeae9c507..728ceeea9 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -784,7 +784,8 @@ namespace MediaBrowser.Api.Images
// Validate first
using (var validationStream = new MemoryStream(bytes))
{
- using (var image = Image.FromStream(validationStream))
+ // This will throw an exception if it's not a valid image
+ using (Image.FromStream(validationStream))
{
}
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index a7cc205ba..df80b465f 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -224,6 +224,7 @@ namespace MediaBrowser.Model.Configuration
EnableEpisodeChapterImageExtraction = false;
EnableOtherVideoChapterImageExtraction = false;
EnableAutomaticRestart = true;
+ EnablePeoplePrefixSubFolders = true;
MinResumePct = 5;
MaxResumePct = 90;
diff --git a/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs
index 64de1c37f..eee6f3b48 100644
--- a/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs
+++ b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.BoxSets
{
///
- /// Class SeriesProviderFromXml
+ /// Class BoxSetXmlProvider.
///
public class BoxSetXmlProvider : BaseXmlProvider, ILocalMetadataProvider
{
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 282facfc8..0ac26330a 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -107,6 +107,8 @@
+
+
@@ -152,22 +154,20 @@
-
-
-
+
+
+
-
+
-
-
-
+
diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
new file mode 100644
index 000000000..70dea5db4
--- /dev/null
+++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
@@ -0,0 +1,200 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Serialization;
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Omdb
+{
+ public class OmdbProvider
+ {
+ private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IHttpClient _httpClient;
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
+ {
+ _jsonSerializer = jsonSerializer;
+ _httpClient = httpClient;
+ }
+
+ public async Task Fetch(BaseItem item, CancellationToken cancellationToken)
+ {
+ var imdbId = item.GetProviderId(MetadataProviders.Imdb);
+
+ if (string.IsNullOrEmpty(imdbId))
+ {
+ return;
+ }
+
+ var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;
+
+ var url = string.Format("http://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam);
+
+ using (var stream = await _httpClient.Get(new HttpRequestOptions
+ {
+ Url = url,
+ ResourcePool = _resourcePool,
+ CancellationToken = cancellationToken
+
+ }).ConfigureAwait(false))
+ {
+ var result = _jsonSerializer.DeserializeFromStream(stream);
+
+ var hasCriticRating = item as IHasCriticRating;
+ if (hasCriticRating != null)
+ {
+ // Seeing some bogus RT data on omdb for series, so filter it out here
+ // RT doesn't even have tv series
+ int tomatoMeter;
+
+ if (!string.IsNullOrEmpty(result.tomatoMeter)
+ && int.TryParse(result.tomatoMeter, NumberStyles.Integer, _usCulture, out tomatoMeter)
+ && tomatoMeter >= 0)
+ {
+ hasCriticRating.CriticRating = tomatoMeter;
+ }
+
+ if (!string.IsNullOrEmpty(result.tomatoConsensus)
+ && !string.Equals(result.tomatoConsensus, "n/a", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase))
+ {
+ hasCriticRating.CriticRatingSummary = WebUtility.HtmlDecode(result.tomatoConsensus);
+ }
+ }
+
+ int voteCount;
+
+ if (!string.IsNullOrEmpty(result.imdbVotes)
+ && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out voteCount)
+ && voteCount >= 0)
+ {
+ item.VoteCount = voteCount;
+ }
+
+ float imdbRating;
+
+ if (!string.IsNullOrEmpty(result.imdbRating)
+ && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out imdbRating)
+ && imdbRating >= 0)
+ {
+ item.CommunityRating = imdbRating;
+ }
+
+ if (!string.IsNullOrEmpty(result.Website)
+ && !string.Equals(result.Website, "n/a", StringComparison.OrdinalIgnoreCase))
+ {
+ item.HomePageUrl = result.Website;
+ }
+
+ ParseAdditionalMetadata(item, result);
+ }
+ }
+
+ private void ParseAdditionalMetadata(BaseItem item, RootObject result)
+ {
+ // Grab series genres because imdb data is better than tvdb. Leave movies alone
+ // But only do it if english is the preferred language because this data will not be localized
+ if (!item.LockedFields.Contains(MetadataFields.Genres) &&
+ ShouldFetchGenres(item) &&
+ !string.IsNullOrWhiteSpace(result.Genre) &&
+ !string.Equals(result.Genre, "n/a", StringComparison.OrdinalIgnoreCase))
+ {
+ item.Genres.Clear();
+
+ foreach (var genre in result.Genre
+ .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(i => i.Trim())
+ .Where(i => !string.IsNullOrWhiteSpace(i)))
+ {
+ item.AddGenre(genre);
+ }
+ }
+
+ var hasMetascore = item as IHasMetascore;
+ if (hasMetascore != null)
+ {
+ float metascore;
+
+ if (!string.IsNullOrEmpty(result.Metascore) && float.TryParse(result.Metascore, NumberStyles.Any, _usCulture, out metascore) && metascore >= 0)
+ {
+ hasMetascore.Metascore = metascore;
+ }
+ }
+
+ var hasAwards = item as IHasAwards;
+ if (hasAwards != null && !string.IsNullOrEmpty(result.Awards) &&
+ !string.Equals(result.Awards, "n/a", StringComparison.OrdinalIgnoreCase))
+ {
+ hasAwards.AwardSummary = WebUtility.HtmlDecode(result.Awards);
+ }
+ }
+
+ private bool ShouldFetchGenres(BaseItem item)
+ {
+ var lang = item.GetPreferredMetadataLanguage();
+
+ // The data isn't localized and so can only be used for english users
+ if (!string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ // Only fetch if other providers didn't get anything
+ if (item is Trailer)
+ {
+ return item.Genres.Count == 0;
+ }
+
+ return item is Series || item is Movie;
+ }
+
+ protected class RootObject
+ {
+ public string Title { get; set; }
+ public string Year { get; set; }
+ public string Rated { get; set; }
+ public string Released { get; set; }
+ public string Runtime { get; set; }
+ public string Genre { get; set; }
+ public string Director { get; set; }
+ public string Writer { get; set; }
+ public string Actors { get; set; }
+ public string Plot { get; set; }
+ public string Poster { get; set; }
+ public string imdbRating { get; set; }
+ public string imdbVotes { get; set; }
+ public string imdbID { get; set; }
+ public string Type { get; set; }
+ public string tomatoMeter { get; set; }
+ public string tomatoImage { get; set; }
+ public string tomatoRating { get; set; }
+ public string tomatoReviews { get; set; }
+ public string tomatoFresh { get; set; }
+ public string tomatoRotten { get; set; }
+ public string tomatoConsensus { get; set; }
+ public string tomatoUserMeter { get; set; }
+ public string tomatoUserRating { get; set; }
+ public string tomatoUserReviews { get; set; }
+ public string DVD { get; set; }
+ public string BoxOffice { get; set; }
+ public string Production { get; set; }
+ public string Website { get; set; }
+ public string Response { get; set; }
+
+ public string Language { get; set; }
+ public string Country { get; set; }
+ public string Awards { get; set; }
+ public string Metascore { get; set; }
+ }
+
+ }
+}
diff --git a/MediaBrowser.Providers/Omdb/OmdbSeriesProvider.cs b/MediaBrowser.Providers/Omdb/OmdbSeriesProvider.cs
new file mode 100644
index 000000000..3659868c7
--- /dev/null
+++ b/MediaBrowser.Providers/Omdb/OmdbSeriesProvider.cs
@@ -0,0 +1,31 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Omdb
+{
+ public class OmdbSeriesProvider : ICustomMetadataProvider
+ {
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IHttpClient _httpClient;
+
+ public OmdbSeriesProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
+ {
+ _jsonSerializer = jsonSerializer;
+ _httpClient = httpClient;
+ }
+
+ public Task FetchAsync(Series item, CancellationToken cancellationToken)
+ {
+ return new OmdbProvider(_jsonSerializer, _httpClient).Fetch(item, cancellationToken);
+ }
+
+ public string Name
+ {
+ get { return "OMDb"; }
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
index 60643252b..b268c08a5 100644
--- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
@@ -20,14 +20,14 @@ using System.Xml;
namespace MediaBrowser.Providers.TV
{
- public class FanartSeasonImageProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
+ public class FanartSeasonProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
- public FanartSeasonImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
+ public FanartSeasonProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
{
_config = config;
_httpClient = httpClient;
@@ -78,9 +78,9 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrEmpty(id) && season.IndexNumber.HasValue)
{
- await FanArtTvProvider.Current.EnsureSeriesXml(id, cancellationToken).ConfigureAwait(false);
+ await FanartSeriesProvider.Current.EnsureSeriesXml(id, cancellationToken).ConfigureAwait(false);
- var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id);
+ var xmlPath = FanartSeriesProvider.Current.GetFanartXmlPath(id);
try
{
@@ -290,7 +290,7 @@ namespace MediaBrowser.Providers.TV
if (!String.IsNullOrEmpty(tvdbId))
{
// Process images
- var imagesXmlPath = FanArtTvProvider.Current.GetFanartXmlPath(tvdbId);
+ var imagesXmlPath = FanartSeriesProvider.Current.GetFanartXmlPath(tvdbId);
var fileInfo = new FileInfo(imagesXmlPath);
diff --git a/MediaBrowser.Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs
deleted file mode 100644
index db71d0db8..000000000
--- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs
+++ /dev/null
@@ -1,331 +0,0 @@
-using MediaBrowser.Common.Configuration;
-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.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Providers;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Net;
-using System.Net;
-using MediaBrowser.Providers.Music;
-
-namespace MediaBrowser.Providers.TV
-{
- class FanArtTvProvider : BaseMetadataProvider
- {
- protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/series/{0}/{1}/xml/all/1/1";
-
- internal static FanArtTvProvider Current { get; private set; }
-
- ///
- /// Gets the HTTP client.
- ///
- /// The HTTP client.
- protected IHttpClient HttpClient { get; private set; }
-
- private readonly IProviderManager _providerManager;
- private readonly IFileSystem _fileSystem;
-
- public FanArtTvProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
- : base(logManager, configurationManager)
- {
- if (httpClient == null)
- {
- throw new ArgumentNullException("httpClient");
- }
- HttpClient = httpClient;
- _providerManager = providerManager;
- _fileSystem = fileSystem;
- Current = this;
- }
-
- public override bool Supports(BaseItem item)
- {
- return item is Series;
- }
-
- ///
- /// Gets the priority.
- ///
- /// The priority.
- public override MetadataProviderPriority Priority
- {
- get { return MetadataProviderPriority.Third; }
- }
-
- public override ItemUpdateType ItemUpdateType
- {
- get
- {
- return ItemUpdateType.ImageUpdate;
- }
- }
-
- ///
- /// Needses the refresh internal.
- ///
- /// The item.
- /// The provider info.
- /// true if XXXX, false otherwise
- protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
- {
- if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tvdb)))
- {
- return false;
- }
-
- return base.NeedsRefreshInternal(item, providerInfo);
- }
-
- protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
- {
- var id = item.GetProviderId(MetadataProviders.Tvdb);
-
- if (!string.IsNullOrEmpty(id))
- {
- // Process images
- var xmlPath = GetFanartXmlPath(id);
-
- var fileInfo = new FileInfo(xmlPath);
-
- return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
- }
-
- return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
- }
-
- ///
- /// Gets a value indicating whether [refresh on version change].
- ///
- /// true if [refresh on version change]; otherwise, false.
- protected override bool RefreshOnVersionChange
- {
- get
- {
- return true;
- }
- }
-
- ///
- /// Gets the provider version.
- ///
- /// The provider version.
- protected override string ProviderVersion
- {
- get
- {
- return "1";
- }
- }
-
- ///
- /// Gets the series data path.
- ///
- /// The app paths.
- /// The series id.
- /// System.String.
- internal static string GetSeriesDataPath(IApplicationPaths appPaths, string seriesId)
- {
- var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
-
- return seriesDataPath;
- }
-
- ///
- /// Gets the series data path.
- ///
- /// The app paths.
- /// System.String.
- internal static string GetSeriesDataPath(IApplicationPaths appPaths)
- {
- var dataPath = Path.Combine(appPaths.DataPath, "fanart-tv");
-
- return dataPath;
- }
-
- public string GetFanartXmlPath(string tvdbId)
- {
- var dataPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, tvdbId);
- return Path.Combine(dataPath, "fanart.xml");
- }
-
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- public override async Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var seriesId = item.GetProviderId(MetadataProviders.Tvdb);
-
- if (!string.IsNullOrEmpty(seriesId))
- {
- var xmlPath = GetFanartXmlPath(seriesId);
-
- // Only download the xml if it doesn't already exist. The prescan task will take care of getting updates
- if (!File.Exists(xmlPath))
- {
- await DownloadSeriesXml(seriesId, cancellationToken).ConfigureAwait(false);
- }
-
- var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartSeriesImageProvider.ProviderName).ConfigureAwait(false);
-
- await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false);
- }
-
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-
- return true;
- }
-
- ///
- /// Fetches from XML.
- ///
- /// The item.
- /// The images.
- /// The cancellation token.
- /// Task.
- private async Task FetchFromXml(BaseItem item, List images, CancellationToken cancellationToken)
- {
- var options = ConfigurationManager.Configuration.GetMetadataOptions("Series") ?? new MetadataOptions();
-
- if (!item.LockedFields.Contains(MetadataFields.Images))
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (options.IsEnabled(ImageType.Primary) && !item.HasImage(ImageType.Primary))
- {
- await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- if (options.IsEnabled(ImageType.Logo) && !item.HasImage(ImageType.Logo))
- {
- await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- if (options.IsEnabled(ImageType.Art) && !item.HasImage(ImageType.Art))
- {
- await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- if (options.IsEnabled(ImageType.Thumb) && !item.HasImage(ImageType.Thumb))
- {
- await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
- {
- await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
- }
- }
-
- if (!item.LockedFields.Contains(MetadataFields.Backdrops))
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var backdropLimit = options.GetLimit(ImageType.Backdrop);
- if (options.IsEnabled(ImageType.Backdrop) &&
- item.BackdropImagePaths.Count < backdropLimit)
- {
- foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
- {
- await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
- .ConfigureAwait(false);
-
- if (item.BackdropImagePaths.Count >= backdropLimit) break;
- }
- }
- }
- }
-
- private async Task SaveImage(BaseItem item, List images, ImageType type, CancellationToken cancellationToken)
- {
- foreach (var image in images.Where(i => i.Type == type))
- {
- try
- {
- await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
- break;
- }
- catch (HttpException ex)
- {
- // Sometimes fanart has bad url's in their xml
- if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
- {
- continue;
- }
- break;
- }
- }
- }
-
- private readonly Task _cachedTask = Task.FromResult(true);
- 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 _cachedTask;
- }
- }
-
- return DownloadSeriesXml(tvdbId, cancellationToken);
- }
-
- ///
- /// Downloads the series XML.
- ///
- /// The TVDB id.
- /// The cancellation token.
- /// Task.
- internal async Task DownloadSeriesXml(string tvdbId, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tvdbId);
-
- var xmlPath = GetFanartXmlPath(tvdbId);
-
- Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
-
- using (var response = await HttpClient.Get(new HttpRequestOptions
- {
- Url = url,
- ResourcePool = FanartArtistProvider.FanArtResourcePool,
- CancellationToken = cancellationToken
-
- }).ConfigureAwait(false))
- {
- using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
- {
- await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
- }
- }
- }
-
- }
-}
diff --git a/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs b/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs
index 6b005c9dc..db546f3a3 100644
--- a/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs
+++ b/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs
@@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.TV
return;
}
- var path = FanArtTvProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
+ var path = FanartSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
Directory.CreateDirectory(path);
@@ -149,8 +149,8 @@ namespace MediaBrowser.Providers.TV
foreach (var id in list)
{
_logger.Info("Updating series " + id);
-
- await FanArtTvProvider.Current.DownloadSeriesXml(id, cancellationToken).ConfigureAwait(false);
+
+ await FanartSeriesProvider.Current.DownloadSeriesXml(id, cancellationToken).ConfigureAwait(false);
numComplete++;
double percent = numComplete;
diff --git a/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs b/MediaBrowser.Providers/TV/FanartSeriesProvider.cs
similarity index 73%
rename from MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
rename to MediaBrowser.Providers/TV/FanartSeriesProvider.cs
index 9e492d8ea..66742cb99 100644
--- a/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/FanartSeriesProvider.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -6,6 +8,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Music;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -15,20 +18,27 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
-using MediaBrowser.Providers.Music;
namespace MediaBrowser.Providers.TV
{
- public class ManualFanartSeriesImageProvider : IRemoteImageProvider, IHasOrder
+ public class FanartSeriesProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
+ private readonly IFileSystem _fileSystem;
- public ManualFanartSeriesImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
+ protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/series/{0}/{1}/xml/all/1/1";
+
+ internal static FanartSeriesProvider Current { get; private set; }
+
+ public FanartSeriesProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
{
_config = config;
_httpClient = httpClient;
+ _fileSystem = fileSystem;
+
+ Current = this;
}
public string Name
@@ -66,7 +76,7 @@ namespace MediaBrowser.Providers.TV
return images.Where(i => i.Type == imageType);
}
- public Task> GetAllImages(IHasImages item, CancellationToken cancellationToken)
+ public async Task> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var list = new List();
@@ -76,7 +86,9 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrEmpty(id))
{
- var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id);
+ await EnsureSeriesXml(id, cancellationToken).ConfigureAwait(false);
+
+ var xmlPath = GetFanartXmlPath(id);
try
{
@@ -93,7 +105,7 @@ namespace MediaBrowser.Providers.TV
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
// Sort first by width to prioritize HD versions
- list = list.OrderByDescending(i => i.Width ?? 0)
+ return list.OrderByDescending(i => i.Width ?? 0)
.ThenByDescending(i =>
{
if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
@@ -114,10 +126,7 @@ namespace MediaBrowser.Providers.TV
return 0;
})
.ThenByDescending(i => i.CommunityRating ?? 0)
- .ThenByDescending(i => i.VoteCount ?? 0)
- .ToList();
-
- return Task.FromResult>(list);
+ .ThenByDescending(i => i.VoteCount ?? 0);
}
private void AddImages(List list, string xmlPath, CancellationToken cancellationToken)
@@ -333,5 +342,102 @@ namespace MediaBrowser.Providers.TV
ResourcePool = FanartArtistProvider.FanArtResourcePool
});
}
+
+ ///
+ /// Gets the series data path.
+ ///
+ /// The app paths.
+ /// The series id.
+ /// System.String.
+ internal static string GetSeriesDataPath(IApplicationPaths appPaths, string seriesId)
+ {
+ var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
+
+ return seriesDataPath;
+ }
+
+ ///
+ /// Gets the series data path.
+ ///
+ /// The app paths.
+ /// System.String.
+ internal static string GetSeriesDataPath(IApplicationPaths appPaths)
+ {
+ var dataPath = Path.Combine(appPaths.DataPath, "fanart-tv");
+
+ return dataPath;
+ }
+
+ public string GetFanartXmlPath(string tvdbId)
+ {
+ var dataPath = GetSeriesDataPath(_config.ApplicationPaths, tvdbId);
+ return Path.Combine(dataPath, "fanart.xml");
+ }
+
+ private readonly Task _cachedTask = Task.FromResult(true);
+ internal Task EnsureSeriesXml(string tvdbId, CancellationToken cancellationToken)
+ {
+ var xmlPath = GetSeriesDataPath(_config.ApplicationPaths, tvdbId);
+
+ var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath);
+
+ if (fileInfo.Exists)
+ {
+ if (_config.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
+ {
+ return _cachedTask;
+ }
+ }
+
+ return DownloadSeriesXml(tvdbId, cancellationToken);
+ }
+
+ ///
+ /// Downloads the series XML.
+ ///
+ /// The TVDB id.
+ /// The cancellation token.
+ /// Task.
+ internal async Task DownloadSeriesXml(string tvdbId, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tvdbId);
+
+ var xmlPath = GetFanartXmlPath(tvdbId);
+
+ Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
+
+ using (var response = await _httpClient.Get(new HttpRequestOptions
+ {
+ Url = url,
+ ResourcePool = FanartArtistProvider.FanArtResourcePool,
+ CancellationToken = cancellationToken
+
+ }).ConfigureAwait(false))
+ {
+ using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
+ {
+ await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
+ }
+ }
+ }
+
+ public bool HasChanged(IHasMetadata item, DateTime date)
+ {
+ var tvdbId = item.GetProviderId(MetadataProviders.Tvdb);
+
+ if (!String.IsNullOrEmpty(tvdbId))
+ {
+ // Process images
+ var imagesXmlPath = GetFanartXmlPath(tvdbId);
+
+ var fileInfo = new FileInfo(imagesXmlPath);
+
+ return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > date;
+ }
+
+ return false;
+ }
}
}
diff --git a/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs
deleted file mode 100644
index 0cc2d8899..000000000
--- a/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs
+++ /dev/null
@@ -1,335 +0,0 @@
-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.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml;
-
-namespace MediaBrowser.Providers.TV
-{
- public class ManualTvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
- {
- private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
- public ManualTvdbSeriesImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
- {
- _config = config;
- _httpClient = httpClient;
- }
-
- public string Name
- {
- get { return ProviderName; }
- }
-
- public static string ProviderName
- {
- get { return "TheTVDB"; }
- }
-
- public bool Supports(IHasImages item)
- {
- return item is Series;
- }
-
- public IEnumerable GetSupportedImages(IHasImages item)
- {
- return new List
- {
- ImageType.Primary,
- ImageType.Banner,
- ImageType.Backdrop
- };
- }
-
- public async Task> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
- {
- var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
-
- return images.Where(i => i.Type == imageType);
- }
-
- public Task> GetAllImages(IHasImages item, CancellationToken cancellationToken)
- {
- var series = (Series)item;
- var seriesId = series.GetProviderId(MetadataProviders.Tvdb);
-
- if (!string.IsNullOrEmpty(seriesId))
- {
- // Process images
- var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);
-
- var path = Path.Combine(seriesDataPath, "banners.xml");
-
- try
- {
- var result = GetImages(path, item.GetPreferredMetadataLanguage(), cancellationToken);
-
- return Task.FromResult(result);
- }
- catch (FileNotFoundException)
- {
- // No tvdb data yet. Don't blow up
- }
- }
-
- return Task.FromResult>(new RemoteImageInfo[] { });
- }
-
- private IEnumerable GetImages(string xmlPath, string preferredLanguage, CancellationToken cancellationToken)
- {
- var settings = new XmlReaderSettings
- {
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true,
- ValidationType = ValidationType.None
- };
-
- var list = new List();
-
- using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
- {
- reader.MoveToContent();
-
- // Loop through each element
- while (reader.Read())
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Banner":
- {
- using (var subtree = reader.ReadSubtree())
- {
- AddImage(subtree, list);
- }
- break;
- }
- default:
- reader.Skip();
- break;
- }
- }
- }
- }
- }
-
- var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
-
- return list.OrderByDescending(i =>
- {
- if (string.Equals(preferredLanguage, 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();
- }
-
- private void AddImage(XmlReader reader, List images)
- {
- reader.MoveToContent();
-
- string bannerType = null;
- string url = null;
- int? bannerSeason = null;
- int? width = null;
- int? height = null;
- string language = null;
- double? rating = null;
- int? voteCount = null;
- string thumbnailUrl = null;
-
- while (reader.Read())
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Rating":
- {
- var val = reader.ReadElementContentAsString() ?? string.Empty;
-
- double rval;
-
- if (double.TryParse(val, NumberStyles.Any, _usCulture, out rval))
- {
- rating = rval;
- }
-
- break;
- }
-
- case "RatingCount":
- {
- var val = reader.ReadElementContentAsString() ?? string.Empty;
-
- int rval;
-
- if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval))
- {
- voteCount = rval;
- }
-
- break;
- }
-
- case "Language":
- {
- language = reader.ReadElementContentAsString() ?? string.Empty;
- break;
- }
-
- case "ThumbnailPath":
- {
- thumbnailUrl = reader.ReadElementContentAsString() ?? string.Empty;
- break;
- }
-
- case "BannerType":
- {
- bannerType = reader.ReadElementContentAsString() ?? string.Empty;
-
- break;
- }
-
- case "BannerPath":
- {
- url = reader.ReadElementContentAsString() ?? string.Empty;
- break;
- }
-
- case "BannerType2":
- {
- var bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
-
- // Sometimes the resolution is stuffed in here
- var resolutionParts = bannerType2.Split('x');
-
- if (resolutionParts.Length == 2)
- {
- int rval;
-
- if (int.TryParse(resolutionParts[0], NumberStyles.Integer, _usCulture, out rval))
- {
- width = rval;
- }
-
- if (int.TryParse(resolutionParts[1], NumberStyles.Integer, _usCulture, out rval))
- {
- height = rval;
- }
-
- }
-
- break;
- }
-
- 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)
- {
- var imageInfo = new RemoteImageInfo
- {
- RatingType = RatingType.Score,
- CommunityRating = rating,
- VoteCount = voteCount,
- Url = TVUtils.BannerUrl + url,
- ProviderName = Name,
- Language = language,
- Width = width,
- Height = height
- };
-
- if (!string.IsNullOrEmpty(thumbnailUrl))
- {
- imageInfo.ThumbnailUrl = TVUtils.BannerUrl + thumbnailUrl;
- }
-
- if (string.Equals(bannerType, "poster", StringComparison.OrdinalIgnoreCase))
- {
- imageInfo.Type = ImageType.Primary;
- images.Add(imageInfo);
- }
- else if (string.Equals(bannerType, "series", StringComparison.OrdinalIgnoreCase))
- {
- imageInfo.Type = ImageType.Banner;
- images.Add(imageInfo);
- }
- else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
- {
- imageInfo.Type = ImageType.Backdrop;
- images.Add(imageInfo);
- }
- }
-
- }
-
- public int Order
- {
- get { return 0; }
- }
-
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url,
- ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
- });
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/SeriesDynamicInfoProvider.cs b/MediaBrowser.Providers/TV/SeriesDynamicInfoProvider.cs
deleted file mode 100644
index ff31ce4aa..000000000
--- a/MediaBrowser.Providers/TV/SeriesDynamicInfoProvider.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Logging;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.TV
-{
- public class SeriesDynamicInfoProvider : BaseMetadataProvider, IDynamicInfoProvider
- {
- public SeriesDynamicInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
- : base(logManager, configurationManager)
- {
- }
-
- public override bool Supports(BaseItem item)
- {
- return item is Series;
- }
-
- public override Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
- {
- var series = (Series)item;
-
- var episodes = series.RecursiveChildren
- .OfType()
- .ToList();
-
- series.DateLastEpisodeAdded = episodes.Select(i => i.DateCreated)
- .OrderByDescending(i => i)
- .FirstOrDefault();
-
- // Don't save to the db
- return FalseTaskResult;
- }
-
- public override MetadataProviderPriority Priority
- {
- get { return MetadataProviderPriority.Last; }
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
new file mode 100644
index 000000000..ffd6d17b2
--- /dev/null
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -0,0 +1,66 @@
+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.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.TV
+{
+ public class SeriesMetadataService : MetadataService
+ {
+ private readonly ILibraryManager _libraryManager;
+
+ public SeriesMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+ : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+ {
+ _libraryManager = libraryManager;
+ }
+
+ ///
+ /// Merges the specified source.
+ ///
+ /// The source.
+ /// The target.
+ /// The locked fields.
+ /// if set to true [replace data].
+ /// if set to true [merge metadata settings].
+ protected override void MergeData(Series source, Series target, List lockedFields, bool replaceData, bool mergeMetadataSettings)
+ {
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+ }
+
+ protected override Task SaveItem(Series item, ItemUpdateType reason, CancellationToken cancellationToken)
+ {
+ return _libraryManager.UpdateItem(item, reason, cancellationToken);
+ }
+
+ protected override ItemUpdateType AfterMetadataRefresh(Series item)
+ {
+ var updateType = base.AfterMetadataRefresh(item);
+
+ var episodes = item.RecursiveChildren
+ .OfType()
+ .ToList();
+
+ var dateLastEpisodeAdded = item.DateLastEpisodeAdded;
+
+ item.DateLastEpisodeAdded = episodes.Select(i => i.DateCreated)
+ .OrderByDescending(i => i)
+ .FirstOrDefault();
+
+ if (dateLastEpisodeAdded != item.DateLastEpisodeAdded)
+ {
+ updateType = updateType | ItemUpdateType.MetadataImport;
+ }
+
+ return updateType;
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/SeriesProviderFromXml.cs b/MediaBrowser.Providers/TV/SeriesProviderFromXml.cs
deleted file mode 100644
index ff99a95c1..000000000
--- a/MediaBrowser.Providers/TV/SeriesProviderFromXml.cs
+++ /dev/null
@@ -1,95 +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
-{
- ///
- /// Class SeriesProviderFromXml
- ///
- public class SeriesProviderFromXml : BaseMetadataProvider
- {
- private readonly IFileSystem _fileSystem;
-
- public SeriesProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
- : base(logManager, configurationManager)
- {
- _fileSystem = fileSystem;
- }
-
- ///
- /// Supportses the specified item.
- ///
- /// The item.
- /// true if XXXX, false otherwise
- public override bool Supports(BaseItem item)
- {
- return item is Series && item.LocationType == LocationType.FileSystem;
- }
-
- ///
- /// Gets the priority.
- ///
- /// The priority.
- public override MetadataProviderPriority Priority
- {
- get { return MetadataProviderPriority.First; }
- }
-
- private const string XmlFileName = "series.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;
- }
-
- ///
- /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
- ///
- /// The item.
- /// if set to true [force].
- /// The cancellation token.
- /// Task{System.Boolean}.
- public override async Task 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 SeriesXmlParser(Logger).Fetch((Series)item, path, cancellationToken);
- }
- finally
- {
- XmlParsingResourcePool.Release();
- }
-
- }
-
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
- return true;
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/SeriesXmlProvider.cs b/MediaBrowser.Providers/TV/SeriesXmlProvider.cs
new file mode 100644
index 000000000..8f0c63136
--- /dev/null
+++ b/MediaBrowser.Providers/TV/SeriesXmlProvider.cs
@@ -0,0 +1,62 @@
+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
+{
+ ///
+ /// Class SeriesProviderFromXml
+ ///
+ public class SeriesXmlProvider : BaseXmlProvider, ILocalMetadataProvider
+ {
+ private readonly ILogger _logger;
+
+ public SeriesXmlProvider(IFileSystem fileSystem, ILogger logger)
+ : base(fileSystem)
+ {
+ _logger = logger;
+ }
+
+ public async Task> GetMetadata(string path, CancellationToken cancellationToken)
+ {
+ path = GetXmlFile(path).FullName;
+
+ var result = new MetadataResult();
+
+ await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ var person = new Series();
+
+ new SeriesXmlParser(_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, "series.xml"));
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs
similarity index 97%
rename from MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs
rename to MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs
index abccc2947..85353bad5 100644
--- a/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs
@@ -17,13 +17,13 @@ using System.Xml;
namespace MediaBrowser.Providers.TV
{
- public class ManualTvdbEpisodeImageProvider : IRemoteImageProvider
+ public class TvdbEpisodeImageProvider : IRemoteImageProvider
{
private readonly IServerConfigurationManager _config;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IHttpClient _httpClient;
- public ManualTvdbEpisodeImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
+ public TvdbEpisodeImageProvider(IServerConfigurationManager config, IHttpClient httpClient)
{
_config = config;
_httpClient = httpClient;
diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
index 73db2680e..e56830644 100644
--- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
@@ -5,211 +5,353 @@ 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 System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using System.Xml;
namespace MediaBrowser.Providers.TV
{
- public class TvdbSeriesImageProvider : BaseMetadataProvider
+ public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor
{
- ///
- /// Gets the HTTP client.
- ///
- /// The HTTP client.
- protected IHttpClient HttpClient { get; private set; }
-
- ///
- /// The _provider manager
- ///
- private readonly IProviderManager _providerManager;
+ private readonly IServerConfigurationManager _config;
+ private readonly IHttpClient _httpClient;
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IFileSystem _fileSystem;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The HTTP client.
- /// The log manager.
- /// The configuration manager.
- /// The provider manager.
- /// httpClient
- public TvdbSeriesImageProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
- : base(logManager, configurationManager)
+ public TvdbSeriesImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
{
- if (httpClient == null)
- {
- throw new ArgumentNullException("httpClient");
- }
- HttpClient = httpClient;
- _providerManager = providerManager;
+ _config = config;
+ _httpClient = httpClient;
_fileSystem = fileSystem;
}
- ///
- /// Supportses the specified item.
- ///
- /// The item.
- /// true if XXXX, false otherwise
- public override bool Supports(BaseItem item)
+ public string Name
+ {
+ get { return ProviderName; }
+ }
+
+ public static string ProviderName
+ {
+ get { return "TheTVDB"; }
+ }
+
+ public bool Supports(IHasImages item)
{
return item is Series;
}
- ///
- /// Gets the priority.
- ///
- /// The priority.
- public override MetadataProviderPriority Priority
+ public IEnumerable GetSupportedImages(IHasImages item)
{
- // Run after fanart
- get { return MetadataProviderPriority.Fourth; }
- }
-
- ///
- /// Gets a value indicating whether [requires internet].
- ///
- /// true if [requires internet]; otherwise, false.
- public override bool RequiresInternet
- {
- get
+ return new List
{
- return true;
- }
+ ImageType.Primary,
+ ImageType.Banner,
+ ImageType.Backdrop
+ };
}
- public override ItemUpdateType ItemUpdateType
+ public async Task> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
- get
- {
- return ItemUpdateType.ImageUpdate;
- }
+ var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
+
+ return images.Where(i => i.Type == imageType);
}
- ///
- /// Gets a value indicating whether [refresh on version change].
- ///
- /// true if [refresh on version change]; otherwise, false.
- protected override bool RefreshOnVersionChange
+ public async Task> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
- get
- {
- return true;
- }
- }
-
- ///
- /// Gets the provider version.
- ///
- /// The provider version.
- protected override string ProviderVersion
- {
- get
- {
- return "1";
- }
- }
-
- protected override DateTime CompareDate(BaseItem item)
- {
- var seriesId = item.GetProviderId(MetadataProviders.Tvdb);
+ var series = (Series)item;
+ var seriesId = series.GetProviderId(MetadataProviders.Tvdb);
if (!string.IsNullOrEmpty(seriesId))
{
+ var language = item.GetPreferredMetadataLanguage();
+
+ await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesId, language, cancellationToken).ConfigureAwait(false);
+
// Process images
- var imagesXmlPath = Path.Combine(TvdbSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "banners.xml");
+ var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);
- var imagesFileInfo = new FileInfo(imagesXmlPath);
+ var path = Path.Combine(seriesDataPath, "banners.xml");
- if (imagesFileInfo.Exists)
+ try
{
- return _fileSystem.GetLastWriteTimeUtc(imagesFileInfo);
+ return GetImages(path, language, cancellationToken);
+ }
+ catch (FileNotFoundException)
+ {
+ // No tvdb data yet. Don't blow up
}
}
- return base.CompareDate(item);
+ return new RemoteImageInfo[] { };
}
- protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+ private IEnumerable GetImages(string xmlPath, string preferredLanguage, CancellationToken cancellationToken)
{
- var options = ConfigurationManager.Configuration.GetMetadataOptions("Series") ?? new MetadataOptions();
-
- if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count >= options.GetLimit(ImageType.Backdrop))
+ var settings = new XmlReaderSettings
{
- return false;
- }
- return base.NeedsRefreshInternal(item, providerInfo);
- }
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true,
+ ValidationType = ValidationType.None
+ };
- ///
- /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
- ///
- /// The item.
- /// if set to true [force].
- /// The cancellation token.
- /// Task{System.Boolean}.
- public override async Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
+ var list = new List();
- var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualTvdbSeriesImageProvider.ProviderName).ConfigureAwait(false);
-
- const int backdropLimit = 1;
-
- await DownloadImages(item, images.ToList(), backdropLimit, cancellationToken).ConfigureAwait(false);
-
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
- return true;
- }
-
- private async Task DownloadImages(BaseItem item, List images, int backdropLimit, CancellationToken cancellationToken)
- {
- var options = ConfigurationManager.Configuration.GetMetadataOptions("Series") ?? new MetadataOptions();
-
- if (!item.LockedFields.Contains(MetadataFields.Images))
+ using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8))
{
- if (!item.HasImage(ImageType.Primary))
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader, settings))
{
- var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
+ reader.MoveToContent();
- if (image != null)
+ // Loop through each element
+ while (reader.Read())
{
- await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
- .ConfigureAwait(false);
- }
- }
+ cancellationToken.ThrowIfCancellationRequested();
- if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
- {
- var image = images.FirstOrDefault(i => i.Type == ImageType.Banner);
-
- if (image != null)
- {
- await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
- .ConfigureAwait(false);
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "Banner":
+ {
+ using (var subtree = reader.ReadSubtree())
+ {
+ AddImage(subtree, list);
+ }
+ break;
+ }
+ default:
+ reader.Skip();
+ break;
+ }
+ }
}
}
}
- if (options.IsEnabled(ImageType.Backdrop) && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
+ var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
+
+ return list.OrderByDescending(i =>
{
- foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop &&
- (!i.Width.HasValue ||
- i.Width.Value >= options.GetMinWidth(ImageType.Backdrop))))
+ if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
{
- var url = backdrop.Url;
+ 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();
+ }
- await _providerManager.SaveImage(item, url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, null, cancellationToken).ConfigureAwait(false);
+ private void AddImage(XmlReader reader, List images)
+ {
+ reader.MoveToContent();
- if (item.BackdropImagePaths.Count >= backdropLimit) break;
+ string bannerType = null;
+ string url = null;
+ int? bannerSeason = null;
+ int? width = null;
+ int? height = null;
+ string language = null;
+ double? rating = null;
+ int? voteCount = null;
+ string thumbnailUrl = null;
+
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "Rating":
+ {
+ var val = reader.ReadElementContentAsString() ?? string.Empty;
+
+ double rval;
+
+ if (double.TryParse(val, NumberStyles.Any, _usCulture, out rval))
+ {
+ rating = rval;
+ }
+
+ break;
+ }
+
+ case "RatingCount":
+ {
+ var val = reader.ReadElementContentAsString() ?? string.Empty;
+
+ int rval;
+
+ if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval))
+ {
+ voteCount = rval;
+ }
+
+ break;
+ }
+
+ case "Language":
+ {
+ language = reader.ReadElementContentAsString() ?? string.Empty;
+ break;
+ }
+
+ case "ThumbnailPath":
+ {
+ thumbnailUrl = reader.ReadElementContentAsString() ?? string.Empty;
+ break;
+ }
+
+ case "BannerType":
+ {
+ bannerType = reader.ReadElementContentAsString() ?? string.Empty;
+
+ break;
+ }
+
+ case "BannerPath":
+ {
+ url = reader.ReadElementContentAsString() ?? string.Empty;
+ break;
+ }
+
+ case "BannerType2":
+ {
+ var bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
+
+ // Sometimes the resolution is stuffed in here
+ var resolutionParts = bannerType2.Split('x');
+
+ if (resolutionParts.Length == 2)
+ {
+ int rval;
+
+ if (int.TryParse(resolutionParts[0], NumberStyles.Integer, _usCulture, out rval))
+ {
+ width = rval;
+ }
+
+ if (int.TryParse(resolutionParts[1], NumberStyles.Integer, _usCulture, out rval))
+ {
+ height = rval;
+ }
+
+ }
+
+ break;
+ }
+
+ 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)
+ {
+ var imageInfo = new RemoteImageInfo
+ {
+ RatingType = RatingType.Score,
+ CommunityRating = rating,
+ VoteCount = voteCount,
+ Url = TVUtils.BannerUrl + url,
+ ProviderName = Name,
+ Language = language,
+ Width = width,
+ Height = height
+ };
+
+ if (!string.IsNullOrEmpty(thumbnailUrl))
+ {
+ imageInfo.ThumbnailUrl = TVUtils.BannerUrl + thumbnailUrl;
+ }
+
+ if (string.Equals(bannerType, "poster", StringComparison.OrdinalIgnoreCase))
+ {
+ imageInfo.Type = ImageType.Primary;
+ images.Add(imageInfo);
+ }
+ else if (string.Equals(bannerType, "series", StringComparison.OrdinalIgnoreCase))
+ {
+ imageInfo.Type = ImageType.Banner;
+ images.Add(imageInfo);
+ }
+ else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
+ {
+ imageInfo.Type = ImageType.Backdrop;
+ images.Add(imageInfo);
+ }
+ }
+
+ }
+
+ public int Order
+ {
+ get { return 0; }
+ }
+
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ {
+ return _httpClient.GetResponse(new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = url,
+ ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
+ });
+ }
+
+ public bool HasChanged(IHasMetadata item, DateTime date)
+ {
+ var tvdbId = item.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;
}
}
}
diff --git a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
index 8d7ef5af9..e4d86f550 100644
--- a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
@@ -4,7 +4,6 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -23,212 +22,55 @@ using System.Xml;
namespace MediaBrowser.Providers.TV
{
- ///
- /// Class RemoteSeriesProvider
- ///
- class TvdbSeriesProvider : BaseMetadataProvider, IDisposable
+ public class TvdbSeriesProvider : IRemoteMetadataProvider, IHasChangeMonitor
{
- ///
- /// The tv db
- ///
internal readonly SemaphoreSlim TvDbResourcePool = new SemaphoreSlim(2, 2);
-
- ///
- /// Gets the current.
- ///
- /// The current.
internal static TvdbSeriesProvider Current { get; private set; }
-
- ///
- /// The _zip client
- ///
private readonly IZipClient _zipClient;
-
- ///
- /// Gets the HTTP client.
- ///
- /// The HTTP client.
- protected IHttpClient HttpClient { get; private set; }
-
+ private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
+ private readonly IServerConfigurationManager _config;
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+ private readonly ILogger _logger;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The HTTP client.
- /// The log manager.
- /// The configuration manager.
- /// The zip client.
- /// httpClient
- public TvdbSeriesProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IZipClient zipClient, IFileSystem fileSystem)
- : base(logManager, configurationManager)
+ public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger)
{
- if (httpClient == null)
- {
- throw new ArgumentNullException("httpClient");
- }
- HttpClient = httpClient;
_zipClient = zipClient;
+ _httpClient = httpClient;
_fileSystem = fileSystem;
+ _config = config;
+ _logger = logger;
Current = this;
}
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- protected virtual void Dispose(bool dispose)
- {
- if (dispose)
- {
- TvDbResourcePool.Dispose();
- }
- }
-
- ///
- /// The root URL
- ///
private const string RootUrl = "http://www.thetvdb.com/api/";
- ///
- /// The series query
- ///
private const string SeriesQuery = "GetSeries.php?seriesname={0}";
- ///
- /// The series get zip
- ///
private const string SeriesGetZip = "http://www.thetvdb.com/api/{0}/series/{1}/all/{2}.zip";
- ///
- /// The LOCA l_ MET a_ FIL e_ NAME
- ///
- protected const string LocalMetaFileName = "series.xml";
-
- ///
- /// Supportses the specified item.
- ///
- /// The item.
- /// true if XXXX, false otherwise
- public override bool Supports(BaseItem item)
+ public async Task> GetMetadata(ItemId itemId, CancellationToken cancellationToken)
{
- return item is Series;
- }
+ var result = new MetadataResult();
- ///
- /// Gets the priority.
- ///
- /// The priority.
- public override MetadataProviderPriority Priority
- {
- get { return MetadataProviderPriority.Second; }
- }
-
- ///
- /// Gets a value indicating whether [requires internet].
- ///
- /// true if [requires internet]; otherwise, false.
- public override bool RequiresInternet
- {
- get
- {
- return true;
- }
- }
-
- ///
- /// Gets a value indicating whether [refresh on version change].
- ///
- /// true if [refresh on version change]; otherwise, false.
- protected override bool RefreshOnVersionChange
- {
- get
- {
- return true;
- }
- }
-
- ///
- /// Gets the provider version.
- ///
- /// The provider version.
- protected override string ProviderVersion
- {
- get
- {
- return "2";
- }
- }
-
- public override bool EnforceDontFetchMetadata
- {
- get
- {
- // Other providers depend on the xml downloaded here
- return false;
- }
- }
-
- protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
- {
- var seriesId = item.GetProviderId(MetadataProviders.Tvdb);
-
- if (!string.IsNullOrEmpty(seriesId))
- {
- // Process images
- var path = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId);
-
- try
- {
- var files = new DirectoryInfo(path)
- .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
- .Select(i => _fileSystem.GetLastWriteTimeUtc(i))
- .ToList();
-
- if (files.Count > 0)
- {
- return files.Max() > providerInfo.LastRefreshed;
- }
- }
- catch (DirectoryNotFoundException)
- {
- // Don't blow up
- return true;
- }
- }
-
- return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
- }
-
- ///
- /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
- ///
- /// The item.
- /// if set to true [force].
- /// The cancellation token.
- /// Task{System.Boolean}.
- public override async Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var series = (Series)item;
-
- var seriesId = series.GetProviderId(MetadataProviders.Tvdb);
+ var seriesId = itemId.GetProviderId(MetadataProviders.Tvdb);
if (string.IsNullOrEmpty(seriesId))
{
- seriesId = await FindSeries(series.Name, cancellationToken).ConfigureAwait(false);
+ seriesId = await FindSeries(itemId.Name, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
if (!string.IsNullOrEmpty(seriesId))
{
- var seriesDataPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId);
+ await EnsureSeriesInfo(seriesId, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false);
- await FetchSeriesData(series, seriesId, seriesDataPath, force, cancellationToken).ConfigureAwait(false);
+ result.Item = new Series();
+ result.HasMetadata = true;
+
+ FetchSeriesData(result.Item, seriesId, cancellationToken);
}
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
- return true;
+ return result;
}
///
@@ -236,48 +78,24 @@ namespace MediaBrowser.Providers.TV
///
/// The series.
/// The series id.
- /// The series data path.
- /// if set to true [is forced refresh].
/// The cancellation token.
/// Task{System.Boolean}.
- private async Task FetchSeriesData(Series series, string seriesId, string seriesDataPath, bool isForcedRefresh, CancellationToken cancellationToken)
+ private void FetchSeriesData(Series series, string seriesId, CancellationToken cancellationToken)
{
- Directory.CreateDirectory(seriesDataPath);
+ series.SetProviderId(MetadataProviders.Tvdb, seriesId);
- var files = Directory.EnumerateFiles(seriesDataPath, "*.xml", SearchOption.TopDirectoryOnly)
- .Select(Path.GetFileName)
- .ToList();
+ var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesId);
var seriesXmlFilename = series.GetPreferredMetadataLanguage().ToLower() + ".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, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
- }
+ var seriesXmlPath = Path.Combine(seriesDataPath, seriesXmlFilename);
+ var actorsXmlPath = Path.Combine(seriesDataPath, "actors.xml");
- // Have to check this here since we prevent the normal enforcement through ProviderManager
- if (!series.DontFetchMeta)
- {
- // Examine if there's no local metadata, or save local is on (to get updates)
- if (isForcedRefresh || ConfigurationManager.Configuration.EnableTvDbUpdates || !HasLocalMeta(series))
- {
- series.SetProviderId(MetadataProviders.Tvdb, seriesId);
+ FetchSeriesInfo(series, seriesXmlPath, cancellationToken);
- var seriesXmlPath = Path.Combine(seriesDataPath, seriesXmlFilename);
- var actorsXmlPath = Path.Combine(seriesDataPath, "actors.xml");
+ cancellationToken.ThrowIfCancellationRequested();
- FetchSeriesInfo(series, seriesXmlPath, cancellationToken);
-
- if (!series.LockedFields.Contains(MetadataFields.Cast))
- {
- series.People.Clear();
-
- FetchActors(series, actorsXmlPath, cancellationToken);
- }
- }
- }
+ FetchActors(series, actorsXmlPath);
}
///
@@ -293,7 +111,7 @@ namespace MediaBrowser.Providers.TV
{
var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, preferredMetadataLanguage);
- using (var zipStream = await HttpClient.Get(new HttpRequestOptions
+ using (var zipStream = await _httpClient.Get(new HttpRequestOptions
{
Url = url,
ResourcePool = TvDbResourcePool,
@@ -326,7 +144,7 @@ namespace MediaBrowser.Providers.TV
internal async Task EnsureSeriesInfo(string seriesId, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
- var seriesDataPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId);
+ var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesId);
Directory.CreateDirectory(seriesDataPath);
@@ -344,76 +162,606 @@ namespace MediaBrowser.Providers.TV
}
}
- private void DeleteXmlFiles(string path)
+ ///
+ /// Finds the series.
+ ///
+ /// The name.
+ /// The cancellation token.
+ /// Task{System.String}.
+ private async Task FindSeries(string name, CancellationToken cancellationToken)
{
- try
+ var url = string.Format(RootUrl + SeriesQuery, WebUtility.UrlEncode(name));
+ var doc = new XmlDocument();
+
+ using (var results = await _httpClient.Get(new HttpRequestOptions
{
- foreach (var file in new DirectoryInfo(path)
- .EnumerateFiles("*.xml", SearchOption.AllDirectories)
- .ToList())
+ Url = url,
+ ResourcePool = TvDbResourcePool,
+ CancellationToken = cancellationToken
+
+ }).ConfigureAwait(false))
+ {
+ doc.Load(results);
+ }
+
+ if (doc.HasChildNodes)
+ {
+ var nodes = doc.SelectNodes("//Series");
+ var comparableName = GetComparableName(name);
+ if (nodes != null)
{
- file.Delete();
+ foreach (XmlNode node in nodes)
+ {
+ var titles = new List();
+
+ var nameNode = node.SelectSingleNode("./SeriesName");
+ if (nameNode != null)
+ {
+ titles.Add(GetComparableName(nameNode.InnerText));
+ }
+
+ var aliasNode = node.SelectSingleNode("./AliasNames");
+ if (aliasNode != null)
+ {
+ var alias = aliasNode.InnerText.Split('|').Select(GetComparableName);
+ titles.AddRange(alias);
+ }
+
+ if (titles.Any(t => string.Equals(t, comparableName, StringComparison.OrdinalIgnoreCase)))
+ {
+ var id = node.SelectSingleNode("./seriesid");
+ if (id != null)
+ return id.InnerText;
+ }
+
+ foreach (var title in titles)
+ {
+ _logger.Info("TVDb Provider - " + title + " did not match " + comparableName);
+ }
+ }
}
}
- catch (DirectoryNotFoundException)
+
+ // Try stripping off the year if it was supplied
+ var parenthIndex = name.LastIndexOf('(');
+
+ if (parenthIndex != -1)
{
- // No biggie
+ var newName = name.Substring(0, parenthIndex);
+
+ return await FindSeries(newName, cancellationToken);
}
+
+ _logger.Info("TVDb Provider - Could not find " + name + ". Check name on Thetvdb.org.");
+ return null;
}
///
- /// Sanitizes the XML file.
+ /// The remove
///
- /// The file.
- /// Task.
- private async Task SanitizeXmlFile(string file)
- {
- string validXml;
-
- using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true))
- {
- using (var reader = new StreamReader(fileStream))
- {
- var xml = await reader.ReadToEndAsync().ConfigureAwait(false);
-
- validXml = StripInvalidXmlCharacters(xml);
- }
- }
-
- using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read, true))
- {
- using (var writer = new StreamWriter(fileStream))
- {
- await writer.WriteAsync(validXml).ConfigureAwait(false);
- }
- }
- }
+ const string remove = "\"'!`?";
+ ///
+ /// The spacers
+ ///
+ const string spacers = "/,.:;\\(){}[]+-_=–*"; // (there are not actually two - in the they are different char codes)
///
- /// Strips the invalid XML characters.
+ /// Gets the name of the comparable.
///
- /// The in string.
+ /// The name.
/// System.String.
- public static string StripInvalidXmlCharacters(string inString)
+ internal static string GetComparableName(string name)
{
- if (inString == null) return null;
-
- var sbOutput = new StringBuilder();
- char ch;
-
- for (int i = 0; i < inString.Length; i++)
+ name = name.ToLower();
+ name = name.Normalize(NormalizationForm.FormKD);
+ var sb = new StringBuilder();
+ foreach (var c in name)
{
- ch = inString[i];
- if ((ch >= 0x0020 && ch <= 0xD7FF) ||
- (ch >= 0xE000 && ch <= 0xFFFD) ||
- ch == 0x0009 ||
- ch == 0x000A ||
- ch == 0x000D)
+ if ((int)c >= 0x2B0 && (int)c <= 0x0333)
{
- sbOutput.Append(ch);
+ // skip char modifier and diacritics
+ }
+ else if (remove.IndexOf(c) > -1)
+ {
+ // skip chars we are removing
+ }
+ else if (spacers.IndexOf(c) > -1)
+ {
+ sb.Append(" ");
+ }
+ else if (c == '&')
+ {
+ sb.Append(" and ");
+ }
+ else
+ {
+ sb.Append(c);
+ }
+ }
+ name = sb.ToString();
+ name = name.Replace(", the", "");
+ name = name.Replace("the ", " ");
+ name = name.Replace(" the ", " ");
+
+ string prevName;
+ do
+ {
+ prevName = name;
+ name = name.Replace(" ", " ");
+ } while (name.Length != prevName.Length);
+
+ return name.Trim();
+ }
+
+ private void FetchSeriesInfo(Series item, string seriesXmlPath, CancellationToken cancellationToken)
+ {
+ var settings = new XmlReaderSettings
+ {
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true,
+ ValidationType = ValidationType.None
+ };
+
+ var episiodeAirDates = new List();
+
+ using (var streamReader = new StreamReader(seriesXmlPath, Encoding.UTF8))
+ {
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader, settings))
+ {
+ reader.MoveToContent();
+
+ // Loop through each element
+ while (reader.Read())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ 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 (item.Status.HasValue && item.Status.Value == SeriesStatus.Ended && episiodeAirDates.Count > 0)
+ {
+ item.EndDate = episiodeAirDates.Max();
+ }
+ }
+
+ 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.
+ ///
+ /// The series.
+ /// The actors XML path.
+ private void FetchActors(Series series, string actorsXmlPath)
+ {
+ var settings = new XmlReaderSettings
+ {
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true,
+ ValidationType = ValidationType.None
+ };
+
+ using (var streamReader = new StreamReader(actorsXmlPath, 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 "Actor":
+ {
+ using (var subtree = reader.ReadSubtree())
+ {
+ FetchDataFromActorNode(series, subtree);
+ }
+ break;
+ }
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Fetches the data from actor node.
+ ///
+ /// The series.
+ /// The reader.
+ 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;
+ }
+
+ case "SortOrder":
+ {
+ 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))
+ {
+ personInfo.SortOrder = rval;
+ }
+ }
+ break;
+ }
+
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
+
+ personInfo.Type = PersonType.Actor;
+
+ if (!string.IsNullOrEmpty(personInfo.Name))
+ {
+ series.AddPerson(personInfo);
+ }
+ }
+
+ 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":
+ {
+ item.Name = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
+ break;
+ }
+
+ case "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 "RatingCount":
+ {
+ 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))
+ {
+ item.VoteCount = 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))
+ {
+ 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))
+ {
+ var vals = val
+ .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(i => i.Trim())
+ .Where(i => !string.IsNullOrWhiteSpace(i))
+ .ToList();
+
+ if (vals.Count > 0)
+ {
+ item.Genres.Clear();
+
+ foreach (var genre in vals)
+ {
+ item.AddGenre(genre);
+ }
+ }
+ }
+
+ break;
+ }
+
+ case "Network":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ var vals = val
+ .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(i => i.Trim())
+ .Where(i => !string.IsNullOrWhiteSpace(i))
+ .ToList();
+
+ if (vals.Count > 0)
+ {
+ item.Studios.Clear();
+
+ foreach (var genre in vals)
+ {
+ item.AddStudio(genre);
+ }
+ }
+ }
+
+ break;
+ }
+
+ default:
+ reader.Skip();
+ break;
+ }
}
}
- return sbOutput.ToString();
}
///
@@ -506,7 +854,7 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrWhiteSpace(val))
{
int num;
- if (int.TryParse(val, NumberStyles.Integer, UsCulture, out num))
+ if (int.TryParse(val, NumberStyles.Integer, _usCulture, out num))
{
episodeNumber = num;
}
@@ -520,7 +868,7 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrWhiteSpace(val))
{
int num;
- if (int.TryParse(val, NumberStyles.Integer, UsCulture, out num))
+ if (int.TryParse(val, NumberStyles.Integer, _usCulture, out num))
{
absoluteNumber = num;
}
@@ -534,7 +882,7 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrWhiteSpace(val))
{
int num;
- if (int.TryParse(val, NumberStyles.Integer, UsCulture, out num))
+ if (int.TryParse(val, NumberStyles.Integer, _usCulture, out num))
{
seasonNumber = num;
}
@@ -555,7 +903,7 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrEmpty(lastUpdateString) && lastTvDbUpdateTime.HasValue)
{
long num;
- if (long.TryParse(lastUpdateString, NumberStyles.Any, UsCulture, out num))
+ if (long.TryParse(lastUpdateString, NumberStyles.Any, _usCulture, out num))
{
hasEpisodeChanged = num >= lastTvDbUpdateTime.Value;
}
@@ -620,648 +968,119 @@ namespace MediaBrowser.Providers.TV
return dataPath;
}
- private void FetchSeriesInfo(Series item, string seriesXmlPath, CancellationToken cancellationToken)
+ private void DeleteXmlFiles(string path)
{
- var settings = new XmlReaderSettings
+ try
{
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true,
- ValidationType = ValidationType.None
- };
-
- var episiodeAirDates = new List();
-
- using (var streamReader = new StreamReader(seriesXmlPath, Encoding.UTF8))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
+ foreach (var file in new DirectoryInfo(path)
+ .EnumerateFiles("*.xml", SearchOption.AllDirectories)
+ .ToList())
{
- reader.MoveToContent();
-
- // Loop through each element
- while (reader.Read())
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (reader.NodeType == XmlNodeType.Element)
- {
- 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;
- }
- }
- }
+ file.Delete();
}
}
-
- if (item.Status.HasValue && item.Status.Value == SeriesStatus.Ended && episiodeAirDates.Count > 0)
+ catch (DirectoryNotFoundException)
{
- item.EndDate = episiodeAirDates.Max();
+ // No biggie
}
}
- 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))
- {
- if (!item.LockedFields.Contains(MetadataFields.OfficialRating))
- {
- 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 "RatingCount":
- {
- 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))
- {
- item.VoteCount = 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(item.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase)))
- {
- var vals = val
- .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
- .Select(i => i.Trim())
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .ToList();
-
- if (vals.Count > 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))
- .ToList();
-
- if (vals.Count > 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.
+ /// Sanitizes the XML file.
///
- /// The series.
- /// The actors XML path.
- /// The cancellation token.
- private void FetchActors(Series series, string actorsXmlPath, CancellationToken cancellationToken)
+ /// The file.
+ /// Task.
+ private async Task SanitizeXmlFile(string file)
{
- var settings = new XmlReaderSettings
- {
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true,
- ValidationType = ValidationType.None
- };
+ string validXml;
- using (var streamReader = new StreamReader(actorsXmlPath, Encoding.UTF8))
+ using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true))
{
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader, settings))
+ using (var reader = new StreamReader(fileStream))
{
- reader.MoveToContent();
+ var xml = await reader.ReadToEndAsync().ConfigureAwait(false);
- // Loop through each element
- while (reader.Read())
- {
- cancellationToken.ThrowIfCancellationRequested();
+ validXml = StripInvalidXmlCharacters(xml);
+ }
+ }
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Actor":
- {
- using (var subtree = reader.ReadSubtree())
- {
- FetchDataFromActorNode(series, subtree);
- }
- break;
- }
- default:
- reader.Skip();
- break;
- }
- }
- }
+ using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read, true))
+ {
+ using (var writer = new StreamWriter(fileStream))
+ {
+ await writer.WriteAsync(validXml).ConfigureAwait(false);
}
}
}
///
- /// Fetches the data from actor node.
+ /// Strips the invalid XML characters.
///
- /// The series.
- /// The reader.
- 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;
- }
-
- case "SortOrder":
- {
- 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))
- {
- personInfo.SortOrder = rval;
- }
- }
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- }
-
- personInfo.Type = PersonType.Actor;
-
- if (!string.IsNullOrEmpty(personInfo.Name))
- {
- series.AddPerson(personInfo);
- }
- }
-
- ///
- /// The us culture
- ///
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- ///
- /// Determines whether [has local meta] [the specified item].
- ///
- /// The item.
- /// true if [has local meta] [the specified item]; otherwise, false.
- private bool HasLocalMeta(BaseItem item)
- {
- return item.ResolveArgs.ContainsMetaFileByName(LocalMetaFileName);
- }
-
- ///
- /// Finds the series.
- ///
- /// The name.
- /// The cancellation token.
- /// Task{System.String}.
- private async Task FindSeries(string name, CancellationToken cancellationToken)
- {
- var url = string.Format(RootUrl + SeriesQuery, WebUtility.UrlEncode(name));
- var doc = new XmlDocument();
-
- using (var results = await HttpClient.Get(new HttpRequestOptions
- {
- Url = url,
- ResourcePool = TvDbResourcePool,
- CancellationToken = cancellationToken
-
- }).ConfigureAwait(false))
- {
- doc.Load(results);
- }
-
- if (doc.HasChildNodes)
- {
- var nodes = doc.SelectNodes("//Series");
- var comparableName = GetComparableName(name);
- if (nodes != null)
- {
- foreach (XmlNode node in nodes)
- {
- var titles = new List();
-
- var nameNode = node.SelectSingleNode("./SeriesName");
- if (nameNode != null)
- {
- titles.Add(GetComparableName(nameNode.InnerText));
- }
-
- var aliasNode = node.SelectSingleNode("./AliasNames");
- if (aliasNode != null)
- {
- var alias = aliasNode.InnerText.Split('|').Select(GetComparableName);
- titles.AddRange(alias);
- }
-
- if (titles.Any(t => string.Equals(t, comparableName, StringComparison.OrdinalIgnoreCase)))
- {
- var id = node.SelectSingleNode("./seriesid");
- if (id != null)
- return id.InnerText;
- }
-
- foreach (var title in titles)
- {
- Logger.Info("TVDb Provider - " + title + " did not match " + comparableName);
- }
- }
- }
- }
-
- // Try stripping off the year if it was supplied
- var parenthIndex = name.LastIndexOf('(');
-
- if (parenthIndex != -1)
- {
- var newName = name.Substring(0, parenthIndex);
-
- return await FindSeries(newName, cancellationToken);
- }
-
- Logger.Info("TVDb Provider - Could not find " + name + ". Check name on Thetvdb.org.");
- return null;
- }
-
- ///
- /// The remove
- ///
- const string remove = "\"'!`?";
- ///
- /// The spacers
- ///
- const string spacers = "/,.:;\\(){}[]+-_=–*"; // (there are not actually two - in the they are different char codes)
-
- ///
- /// Gets the name of the comparable.
- ///
- /// The name.
+ /// The in string.
/// System.String.
- internal static string GetComparableName(string name)
+ public static string StripInvalidXmlCharacters(string inString)
{
- name = name.ToLower();
- name = name.Normalize(NormalizationForm.FormKD);
- var sb = new StringBuilder();
- foreach (var c in name)
+ if (inString == null) return null;
+
+ var sbOutput = new StringBuilder();
+ char ch;
+
+ for (int i = 0; i < inString.Length; i++)
{
- if ((int)c >= 0x2B0 && (int)c <= 0x0333)
+ ch = inString[i];
+ if ((ch >= 0x0020 && ch <= 0xD7FF) ||
+ (ch >= 0xE000 && ch <= 0xFFFD) ||
+ ch == 0x0009 ||
+ ch == 0x000A ||
+ ch == 0x000D)
{
- // skip char modifier and diacritics
- }
- else if (remove.IndexOf(c) > -1)
- {
- // skip chars we are removing
- }
- else if (spacers.IndexOf(c) > -1)
- {
- sb.Append(" ");
- }
- else if (c == '&')
- {
- sb.Append(" and ");
- }
- else
- {
- sb.Append(c);
+ sbOutput.Append(ch);
}
}
- name = sb.ToString();
- name = name.Replace(", the", "");
- name = name.Replace("the ", " ");
- name = name.Replace(" the ", " ");
-
- string prevName;
- do
- {
- prevName = name;
- name = name.Replace(" ", " ");
- } while (name.Length != prevName.Length);
-
- return name.Trim();
+ return sbOutput.ToString();
}
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- public void Dispose()
+ public string Name
{
- Dispose(true);
+ get { return "TheTVDB"; }
+ }
+
+ public bool HasChanged(IHasMetadata item, DateTime date)
+ {
+ var seriesId = item.GetProviderId(MetadataProviders.Tvdb);
+
+ if (!string.IsNullOrEmpty(seriesId))
+ {
+ var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesId);
+
+ try
+ {
+ var files = new DirectoryInfo(seriesDataPath).EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
+ .ToList();
+
+ var seriesXmlFilename = item.GetPreferredMetadataLanguage() + ".xml";
+
+ var seriesFile = files.FirstOrDefault(i => string.Equals(seriesXmlFilename, i.Name, StringComparison.OrdinalIgnoreCase));
+
+ if (seriesFile != null && seriesFile.Exists && _fileSystem.GetLastWriteTimeUtc(seriesFile) > date)
+ {
+ return true;
+ }
+
+ var actorsXml = files.FirstOrDefault(i => string.Equals("actors.xml", i.Name, StringComparison.OrdinalIgnoreCase));
+
+ if (actorsXml != null && actorsXml.Exists && _fileSystem.GetLastWriteTimeUtc(actorsXml) > date)
+ {
+ return true;
+ }
+ }
+ catch (DirectoryNotFoundException)
+ {
+ // Don't blow up
+ }
+ }
+
+ return false;
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index 789cc1100..1e04f7e09 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -208,7 +208,6 @@ namespace MediaBrowser.Server.Implementations.Library
/// The prescan tasks.
/// The postscan tasks.
/// The people prescan tasks.
- /// The savers.
public void AddParts(IEnumerable rules,
IEnumerable pluginFolders,
IEnumerable resolvers,
@@ -277,7 +276,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// The configuration.
private void RecordConfigurationValues(ServerConfiguration configuration)
{
- _seasonZeroDisplayName = ConfigurationManager.Configuration.SeasonZeroDisplayName;
+ _seasonZeroDisplayName = configuration.SeasonZeroDisplayName;
_itemsByNamePath = ConfigurationManager.ApplicationPaths.ItemsByNamePath;
}
@@ -309,8 +308,10 @@ namespace MediaBrowser.Server.Implementations.Library
await UpdateSeasonZeroNames(newSeasonZeroName, CancellationToken.None).ConfigureAwait(false);
}
- // Any number of configuration settings could change the way the library is refreshed, so do that now
- _taskManager.CancelIfRunningAndQueue();
+ if (seasonZeroNameChanged || ibnPathChanged)
+ {
+ _taskManager.CancelIfRunningAndQueue();
+ }
});
}