mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
reduce requests against tvdb by getting entire series metadata at once
This commit is contained in:
parent
96e8f053b5
commit
f3a7307ebb
@ -96,11 +96,15 @@ namespace MediaBrowser.Controller.Extensions
|
|||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
public static string SafeGetString(this XmlDocument doc, string path, string defaultString)
|
public static string SafeGetString(this XmlDocument doc, string path, string defaultString)
|
||||||
{
|
{
|
||||||
XmlNode rvalNode = doc.SelectSingleNode(path);
|
var rvalNode = doc.SelectSingleNode(path);
|
||||||
if (rvalNode != null && rvalNode.InnerText.Trim().Length > 0)
|
|
||||||
|
if (rvalNode != null)
|
||||||
{
|
{
|
||||||
return rvalNode.InnerText;
|
var text = rvalNode.InnerText;
|
||||||
|
|
||||||
|
return !string.IsNullOrWhiteSpace(text) ? text : defaultString;
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultString;
|
return defaultString;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,10 +128,12 @@ namespace MediaBrowser.Controller.Extensions
|
|||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
public static string SafeGetString(this XmlNode doc, string path, string defaultValue)
|
public static string SafeGetString(this XmlNode doc, string path, string defaultValue)
|
||||||
{
|
{
|
||||||
XmlNode rvalNode = doc.SelectSingleNode(path);
|
var rvalNode = doc.SelectSingleNode(path);
|
||||||
if (rvalNode != null && rvalNode.InnerText.Length > 0)
|
if (rvalNode != null)
|
||||||
{
|
{
|
||||||
return rvalNode.InnerText;
|
var text = rvalNode.InnerText;
|
||||||
|
|
||||||
|
return !string.IsNullOrWhiteSpace(text) ? text : defaultValue;
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,9 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace MediaBrowser.Controller.Library
|
namespace MediaBrowser.Controller.Library
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface ILibraryManager
|
||||||
|
/// </summary>
|
||||||
public interface ILibraryManager
|
public interface ILibraryManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -140,11 +143,13 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// <param name="resolvers">The resolvers.</param>
|
/// <param name="resolvers">The resolvers.</param>
|
||||||
/// <param name="introProviders">The intro providers.</param>
|
/// <param name="introProviders">The intro providers.</param>
|
||||||
/// <param name="itemComparers">The item comparers.</param>
|
/// <param name="itemComparers">The item comparers.</param>
|
||||||
|
/// <param name="prescanTasks">The prescan tasks.</param>
|
||||||
void AddParts(IEnumerable<IResolverIgnoreRule> rules,
|
void AddParts(IEnumerable<IResolverIgnoreRule> rules,
|
||||||
IEnumerable<IVirtualFolderCreator> pluginFolders,
|
IEnumerable<IVirtualFolderCreator> pluginFolders,
|
||||||
IEnumerable<IItemResolver> resolvers,
|
IEnumerable<IItemResolver> resolvers,
|
||||||
IEnumerable<IIntroProvider> introProviders,
|
IEnumerable<IIntroProvider> introProviders,
|
||||||
IEnumerable<IBaseItemComparer> itemComparers);
|
IEnumerable<IBaseItemComparer> itemComparers,
|
||||||
|
IEnumerable<ILibraryPrescanTask> prescanTasks);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sorts the specified items.
|
/// Sorts the specified items.
|
||||||
@ -160,7 +165,7 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensure supplied item has only one instance throughout
|
/// Ensure supplied item has only one instance throughout
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item"></param>
|
/// <param name="item">The item.</param>
|
||||||
/// <returns>The proper instance to the item</returns>
|
/// <returns>The proper instance to the item</returns>
|
||||||
BaseItem GetOrAddByReferenceItem(BaseItem item);
|
BaseItem GetOrAddByReferenceItem(BaseItem item);
|
||||||
|
|
||||||
@ -186,7 +191,7 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task UpdateItem(BaseItem item, CancellationToken cancellationToken);
|
Task UpdateItem(BaseItem item, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the item.
|
/// Retrieves the item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
20
MediaBrowser.Controller/Library/ILibraryPrescanTask.cs
Normal file
20
MediaBrowser.Controller/Library/ILibraryPrescanTask.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.Library
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface for tasks that run prior to the media library scan
|
||||||
|
/// </summary>
|
||||||
|
public interface ILibraryPrescanTask
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the specified progress.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
Task Run(IProgress<double> progress, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
using System.Globalization;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@ -243,7 +243,7 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fullPath">The full path.</param>
|
/// <param name="fullPath">The full path.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
public static string SeasonNumberFromEpisodeFile(string fullPath)
|
public static int? GetSeasonNumberFromEpisodeFile(string fullPath)
|
||||||
{
|
{
|
||||||
string fl = fullPath.ToLower();
|
string fl = fullPath.ToLower();
|
||||||
foreach (var r in EpisodeExpressions)
|
foreach (var r in EpisodeExpressions)
|
||||||
@ -253,7 +253,19 @@ namespace MediaBrowser.Controller.Library
|
|||||||
{
|
{
|
||||||
Group g = m.Groups["seasonnumber"];
|
Group g = m.Groups["seasonnumber"];
|
||||||
if (g != null)
|
if (g != null)
|
||||||
return g.Value;
|
{
|
||||||
|
var val = g.Value;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(val))
|
||||||
|
{
|
||||||
|
int num;
|
||||||
|
|
||||||
|
if (int.TryParse(val, NumberStyles.Integer, UsCulture, out num))
|
||||||
|
{
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,9 @@
|
|||||||
<Compile Include="Configuration\IServerConfigurationManager.cs" />
|
<Compile Include="Configuration\IServerConfigurationManager.cs" />
|
||||||
<Compile Include="Dto\SessionInfoDtoBuilder.cs" />
|
<Compile Include="Dto\SessionInfoDtoBuilder.cs" />
|
||||||
<Compile Include="Entities\Audio\MusicAlbumDisc.cs" />
|
<Compile Include="Entities\Audio\MusicAlbumDisc.cs" />
|
||||||
|
<Compile Include="Library\ILibraryPrescanTask.cs" />
|
||||||
<Compile Include="Providers\Movies\MovieDbImagesProvider.cs" />
|
<Compile Include="Providers\Movies\MovieDbImagesProvider.cs" />
|
||||||
|
<Compile Include="Providers\TV\TvdbPrescanTask.cs" />
|
||||||
<Compile Include="Session\ISessionManager.cs" />
|
<Compile Include="Session\ISessionManager.cs" />
|
||||||
<Compile Include="Drawing\ImageExtensions.cs" />
|
<Compile Include="Drawing\ImageExtensions.cs" />
|
||||||
<Compile Include="Drawing\ImageHeader.cs" />
|
<Compile Include="Drawing\ImageHeader.cs" />
|
||||||
|
@ -233,7 +233,7 @@ namespace MediaBrowser.Controller.Providers.Movies
|
|||||||
|
|
||||||
var status = ProviderRefreshStatus.Success;
|
var status = ProviderRefreshStatus.Success;
|
||||||
|
|
||||||
var hasLocalPoster = item.LocationType == LocationType.FileSystem ? item.HasLocalImage("folder") : item.HasImage(ImageType.Primary);
|
var hasLocalPoster = item.HasImage(ImageType.Primary);
|
||||||
|
|
||||||
// poster
|
// poster
|
||||||
if (images.posters != null && images.posters.Count > 0 && (ConfigurationManager.Configuration.RefreshItemImages || !hasLocalPoster))
|
if (images.posters != null && images.posters.Count > 0 && (ConfigurationManager.Configuration.RefreshItemImages || !hasLocalPoster))
|
||||||
@ -290,7 +290,7 @@ namespace MediaBrowser.Controller.Providers.Movies
|
|||||||
{
|
{
|
||||||
var bdName = "backdrop" + (i == 0 ? "" : i.ToString(CultureInfo.InvariantCulture));
|
var bdName = "backdrop" + (i == 0 ? "" : i.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
var hasLocalBackdrop = item.LocationType == LocationType.FileSystem ? item.HasLocalImage(bdName) : item.BackdropImagePaths.Count > i;
|
var hasLocalBackdrop = item.BackdropImagePaths.Count > i;
|
||||||
|
|
||||||
if (ConfigurationManager.Configuration.RefreshItemImages || !hasLocalBackdrop)
|
if (ConfigurationManager.Configuration.RefreshItemImages || !hasLocalBackdrop)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
@ -22,8 +23,11 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class RemoteEpisodeProvider : BaseMetadataProvider
|
class RemoteEpisodeProvider : BaseMetadataProvider
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The _provider manager
|
||||||
|
/// </summary>
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the HTTP client.
|
/// Gets the HTTP client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -36,6 +40,7 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
/// <param name="httpClient">The HTTP client.</param>
|
/// <param name="httpClient">The HTTP client.</param>
|
||||||
/// <param name="logManager">The log manager.</param>
|
/// <param name="logManager">The log manager.</param>
|
||||||
/// <param name="configurationManager">The configuration manager.</param>
|
/// <param name="configurationManager">The configuration manager.</param>
|
||||||
|
/// <param name="providerManager">The provider manager.</param>
|
||||||
public RemoteEpisodeProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
|
public RemoteEpisodeProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
|
||||||
: base(logManager, configurationManager)
|
: base(logManager, configurationManager)
|
||||||
{
|
{
|
||||||
@ -80,6 +85,10 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
get { return true; }
|
get { return true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
|
||||||
protected override bool RefreshOnFileSystemStampChange
|
protected override bool RefreshOnFileSystemStampChange
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -88,6 +97,30 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether [refresh on version change].
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
|
||||||
|
protected override bool RefreshOnVersionChange
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the provider version.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The provider version.</value>
|
||||||
|
protected override string ProviderVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Needses the refresh internal.
|
/// Needses the refresh internal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -101,34 +134,102 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GetComparisonData(item) != providerInfo.Data)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return base.NeedsRefreshInternal(item, providerInfo);
|
return base.NeedsRefreshInternal(item, providerInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the comparison data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <returns>Guid.</returns>
|
||||||
|
private Guid GetComparisonData(BaseItem item)
|
||||||
|
{
|
||||||
|
var episode = (Episode)item;
|
||||||
|
|
||||||
|
var seriesId = episode.Series != null ? episode.Series.GetProviderId(MetadataProviders.Tvdb) : null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(seriesId))
|
||||||
|
{
|
||||||
|
// Process images
|
||||||
|
var seriesXmlPath = Path.Combine(RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower() + ".xml");
|
||||||
|
|
||||||
|
var seriesXmlFileInfo = new FileInfo(seriesXmlPath);
|
||||||
|
|
||||||
|
return GetComparisonData(seriesXmlFileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Guid.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the comparison data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seriesXmlFileInfo">The series XML file info.</param>
|
||||||
|
/// <returns>Guid.</returns>
|
||||||
|
private Guid GetComparisonData(FileInfo seriesXmlFileInfo)
|
||||||
|
{
|
||||||
|
var date = seriesXmlFileInfo.Exists ? seriesXmlFileInfo.LastWriteTimeUtc : DateTime.MinValue;
|
||||||
|
|
||||||
|
var key = date.Ticks + seriesXmlFileInfo.FullName;
|
||||||
|
|
||||||
|
return key.GetMD5();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
|
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <param name="force">if set to <c>true</c> [force].</param>
|
/// <param name="force">if set to <c>true</c> [force].</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task{System.Boolean}.</returns>
|
/// <returns>Task{System.Boolean}.</returns>
|
||||||
public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
|
public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (HasLocalMeta(item))
|
||||||
|
|
||||||
var episode = (Episode)item;
|
|
||||||
if (!HasLocalMeta(episode))
|
|
||||||
{
|
{
|
||||||
var seriesId = episode.Series != null ? episode.Series.GetProviderId(MetadataProviders.Tvdb) : null;
|
|
||||||
|
|
||||||
if (seriesId != null)
|
|
||||||
{
|
|
||||||
var status = await FetchEpisodeData(episode, seriesId, cancellationToken).ConfigureAwait(false);
|
|
||||||
SetLastRefreshed(item, DateTime.UtcNow, status);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Logger.Info("Episode provider not fetching because series does not have a tvdb id: " + item.Path);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Logger.Info("Episode provider not fetching because local meta exists or requested to ignore: " + item.Name);
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var episode = (Episode)item;
|
||||||
|
|
||||||
|
var seriesId = episode.Series != null ? episode.Series.GetProviderId(MetadataProviders.Tvdb) : null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(seriesId))
|
||||||
|
{
|
||||||
|
var seriesXmlPath = Path.Combine(RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower() + ".xml");
|
||||||
|
|
||||||
|
var seriesXmlFileInfo = new FileInfo(seriesXmlPath);
|
||||||
|
|
||||||
|
var status = ProviderRefreshStatus.Success;
|
||||||
|
|
||||||
|
if (seriesXmlFileInfo.Exists)
|
||||||
|
{
|
||||||
|
var xmlDoc = new XmlDocument();
|
||||||
|
xmlDoc.Load(seriesXmlPath);
|
||||||
|
|
||||||
|
status = await FetchEpisodeData(xmlDoc, episode, seriesId, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseProviderInfo data;
|
||||||
|
if (!item.ProviderData.TryGetValue(Id, out data))
|
||||||
|
{
|
||||||
|
data = new BaseProviderInfo();
|
||||||
|
item.ProviderData[Id] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Data = GetComparisonData(seriesXmlFileInfo);
|
||||||
|
|
||||||
|
SetLastRefreshed(item, DateTime.UtcNow, status);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info("Episode provider not fetching because series does not have a tvdb id: " + item.Path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,162 +237,121 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches the episode data.
|
/// Fetches the episode data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="seriesXml">The series XML.</param>
|
||||||
/// <param name="episode">The episode.</param>
|
/// <param name="episode">The episode.</param>
|
||||||
/// <param name="seriesId">The series id.</param>
|
/// <param name="seriesId">The series id.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task{System.Boolean}.</returns>
|
/// <returns>Task{System.Boolean}.</returns>
|
||||||
private async Task<ProviderRefreshStatus> FetchEpisodeData(Episode episode, string seriesId, CancellationToken cancellationToken)
|
private async Task<ProviderRefreshStatus> FetchEpisodeData(XmlDocument seriesXml, Episode episode, string seriesId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
string location = episode.Path;
|
|
||||||
|
|
||||||
var episodeNumber = episode.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(location, episode.Season != null);
|
|
||||||
|
|
||||||
var status = ProviderRefreshStatus.Success;
|
var status = ProviderRefreshStatus.Success;
|
||||||
|
|
||||||
if (episodeNumber == null)
|
if (episode.IndexNumber == null)
|
||||||
{
|
{
|
||||||
Logger.Warn("TvDbProvider: Could not determine episode number for: " + episode.Path);
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
episode.IndexNumber = episodeNumber;
|
var seasonNumber = episode.ParentIndexNumber ?? TVUtils.GetSeasonNumberFromEpisodeFile(episode.Path);
|
||||||
var usingAbsoluteData = false;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(seriesId)) return status;
|
if (seasonNumber == null)
|
||||||
|
|
||||||
var seasonNumber = "";
|
|
||||||
if (episode.Parent is Season)
|
|
||||||
{
|
{
|
||||||
seasonNumber = episode.Parent.IndexNumber.ToString();
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(seasonNumber))
|
var usingAbsoluteData = false;
|
||||||
seasonNumber = TVUtils.SeasonNumberFromEpisodeFile(location); // try and extract the season number from the file name for S1E1, 1x04 etc.
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(seasonNumber))
|
var episodeNode = seriesXml.SelectSingleNode("//Episode[EpisodeNumber='" + episode.IndexNumber.Value + "'][SeasonNumber='" + seasonNumber.Value + "']");
|
||||||
|
|
||||||
|
if (episodeNode == null)
|
||||||
{
|
{
|
||||||
seasonNumber = seasonNumber.TrimStart('0');
|
if (seasonNumber.Value == 1)
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(seasonNumber))
|
|
||||||
{
|
{
|
||||||
seasonNumber = "0"; // Specials
|
episodeNode = seriesXml.SelectSingleNode("//Episode[absolute_number='" + episode.IndexNumber.Value + "']");
|
||||||
|
usingAbsoluteData = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var url = string.Format(EpisodeQuery, TVUtils.TvdbApiKey, seriesId, seasonNumber, episodeNumber, ConfigurationManager.Configuration.PreferredMetadataLanguage);
|
// If still null, nothing we can do
|
||||||
var doc = new XmlDocument();
|
if (episodeNode == null)
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
using (var result = await HttpClient.Get(new HttpRequestOptions
|
var doc = new XmlDocument();
|
||||||
|
doc.LoadXml(episodeNode.OuterXml);
|
||||||
|
|
||||||
|
if (!episode.HasImage(ImageType.Primary))
|
||||||
|
{
|
||||||
|
var p = doc.SafeGetString("//filename");
|
||||||
|
if (p != null)
|
||||||
{
|
{
|
||||||
Url = url,
|
if (!Directory.Exists(episode.MetaLocation)) Directory.CreateDirectory(episode.MetaLocation);
|
||||||
ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
EnableResponseCache = true
|
|
||||||
|
|
||||||
}).ConfigureAwait(false))
|
try
|
||||||
{
|
|
||||||
doc.Load(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
//episode does not exist under this season, try absolute numbering.
|
|
||||||
//still assuming it's numbered as 1x01
|
|
||||||
//this is basicly just for anime.
|
|
||||||
if (!doc.HasChildNodes && Int32.Parse(seasonNumber) == 1)
|
|
||||||
{
|
|
||||||
url = string.Format(AbsEpisodeQuery, TVUtils.TvdbApiKey, seriesId, episodeNumber, ConfigurationManager.Configuration.PreferredMetadataLanguage);
|
|
||||||
|
|
||||||
using (var result = await HttpClient.Get(new HttpRequestOptions
|
|
||||||
{
|
{
|
||||||
Url = url,
|
episode.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(episode, TVUtils.BannerUrl + p, Path.GetFileName(p), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken);
|
||||||
ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool,
|
}
|
||||||
CancellationToken = cancellationToken,
|
catch (HttpException)
|
||||||
EnableResponseCache = true
|
|
||||||
|
|
||||||
}).ConfigureAwait(false))
|
|
||||||
{
|
{
|
||||||
if (result != null) doc.Load(result);
|
status = ProviderRefreshStatus.CompletedWithErrors;
|
||||||
usingAbsoluteData = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (doc.HasChildNodes)
|
episode.Overview = doc.SafeGetString("//Overview");
|
||||||
|
if (usingAbsoluteData)
|
||||||
|
episode.IndexNumber = doc.SafeGetInt32("//absolute_number", -1);
|
||||||
|
if (episode.IndexNumber < 0)
|
||||||
|
episode.IndexNumber = doc.SafeGetInt32("//EpisodeNumber");
|
||||||
|
|
||||||
|
episode.Name = doc.SafeGetString("//EpisodeName");
|
||||||
|
episode.CommunityRating = doc.SafeGetSingle("//Rating", -1, 10);
|
||||||
|
var firstAired = doc.SafeGetString("//FirstAired");
|
||||||
|
DateTime airDate;
|
||||||
|
if (DateTime.TryParse(firstAired, out airDate) && airDate.Year > 1850)
|
||||||
|
{
|
||||||
|
episode.PremiereDate = airDate.ToUniversalTime();
|
||||||
|
episode.ProductionYear = airDate.Year;
|
||||||
|
}
|
||||||
|
|
||||||
|
episode.People.Clear();
|
||||||
|
|
||||||
|
var actors = doc.SafeGetString("//GuestStars");
|
||||||
|
if (actors != null)
|
||||||
|
{
|
||||||
|
foreach (var person in actors.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(str => new PersonInfo { Type = PersonType.GuestStar, Name = str }))
|
||||||
{
|
{
|
||||||
if (!episode.HasImage(ImageType.Primary))
|
episode.AddPerson(person);
|
||||||
{
|
|
||||||
var p = doc.SafeGetString("//filename");
|
|
||||||
if (p != null)
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(episode.MetaLocation)) Directory.CreateDirectory(episode.MetaLocation);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
episode.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(episode, TVUtils.BannerUrl + p, Path.GetFileName(p), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (HttpException)
|
|
||||||
{
|
|
||||||
status = ProviderRefreshStatus.CompletedWithErrors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
episode.Overview = doc.SafeGetString("//Overview");
|
|
||||||
if (usingAbsoluteData)
|
|
||||||
episode.IndexNumber = doc.SafeGetInt32("//absolute_number", -1);
|
|
||||||
if (episode.IndexNumber < 0)
|
|
||||||
episode.IndexNumber = doc.SafeGetInt32("//EpisodeNumber");
|
|
||||||
|
|
||||||
episode.Name = doc.SafeGetString("//EpisodeName");
|
|
||||||
episode.CommunityRating = doc.SafeGetSingle("//Rating", -1, 10);
|
|
||||||
var firstAired = doc.SafeGetString("//FirstAired");
|
|
||||||
DateTime airDate;
|
|
||||||
if (DateTime.TryParse(firstAired, out airDate) && airDate.Year > 1850)
|
|
||||||
{
|
|
||||||
episode.PremiereDate = airDate.ToUniversalTime();
|
|
||||||
episode.ProductionYear = airDate.Year;
|
|
||||||
}
|
|
||||||
|
|
||||||
episode.People.Clear();
|
|
||||||
|
|
||||||
var actors = doc.SafeGetString("//GuestStars");
|
|
||||||
if (actors != null)
|
|
||||||
{
|
|
||||||
foreach (var person in actors.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(str => new PersonInfo { Type = PersonType.GuestStar, Name = str }))
|
|
||||||
{
|
|
||||||
episode.AddPerson(person);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var directors = doc.SafeGetString("//Director");
|
|
||||||
if (directors != null)
|
|
||||||
{
|
|
||||||
foreach (var person in directors.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(str => new PersonInfo { Type = PersonType.Director, Name = str }))
|
|
||||||
{
|
|
||||||
episode.AddPerson(person);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var writers = doc.SafeGetString("//Writer");
|
|
||||||
if (writers != null)
|
|
||||||
{
|
|
||||||
foreach (var person in writers.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(str => new PersonInfo { Type = PersonType.Writer, Name = str }))
|
|
||||||
{
|
|
||||||
episode.AddPerson(person);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ConfigurationManager.Configuration.SaveLocalMeta)
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(episode.MetaLocation)) Directory.CreateDirectory(episode.MetaLocation);
|
|
||||||
var ms = new MemoryStream();
|
|
||||||
doc.Save(ms);
|
|
||||||
|
|
||||||
await _providerManager.SaveToLibraryFilesystem(episode, Path.Combine(episode.MetaLocation, Path.GetFileNameWithoutExtension(episode.Path) + ".xml"), ms, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var directors = doc.SafeGetString("//Director");
|
||||||
|
if (directors != null)
|
||||||
|
{
|
||||||
|
foreach (var person in directors.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(str => new PersonInfo { Type = PersonType.Director, Name = str }))
|
||||||
|
{
|
||||||
|
episode.AddPerson(person);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var writers = doc.SafeGetString("//Writer");
|
||||||
|
if (writers != null)
|
||||||
|
{
|
||||||
|
foreach (var person in writers.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).Select(str => new PersonInfo { Type = PersonType.Writer, Name = str }))
|
||||||
|
{
|
||||||
|
episode.AddPerson(person);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ConfigurationManager.Configuration.SaveLocalMeta)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(episode.MetaLocation)) Directory.CreateDirectory(episode.MetaLocation);
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
doc.Save(ms);
|
||||||
|
|
||||||
|
await _providerManager.SaveToLibraryFilesystem(episode, Path.Combine(episode.MetaLocation, Path.GetFileNameWithoutExtension(episode.Path) + ".xml"), ms, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
@ -25,8 +26,19 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
/// <value>The HTTP client.</value>
|
/// <value>The HTTP client.</value>
|
||||||
protected IHttpClient HttpClient { get; private set; }
|
protected IHttpClient HttpClient { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _provider manager
|
||||||
|
/// </summary>
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RemoteSeasonProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpClient">The HTTP client.</param>
|
||||||
|
/// <param name="logManager">The log manager.</param>
|
||||||
|
/// <param name="configurationManager">The configuration manager.</param>
|
||||||
|
/// <param name="providerManager">The provider manager.</param>
|
||||||
|
/// <exception cref="System.ArgumentNullException">httpClient</exception>
|
||||||
public RemoteSeasonProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
|
public RemoteSeasonProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
|
||||||
: base(logManager, configurationManager)
|
: base(logManager, configurationManager)
|
||||||
{
|
{
|
||||||
@ -70,6 +82,10 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
|
||||||
protected override bool RefreshOnFileSystemStampChange
|
protected override bool RefreshOnFileSystemStampChange
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -78,6 +94,30 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether [refresh on version change].
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
|
||||||
|
protected override bool RefreshOnVersionChange
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the provider version.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The provider version.</value>
|
||||||
|
protected override string ProviderVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Needses the refresh internal.
|
/// Needses the refresh internal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -86,14 +126,51 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||||
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
|
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
|
||||||
{
|
{
|
||||||
if (HasLocalMeta(item))
|
if (GetComparisonData(item) != providerInfo.Data)
|
||||||
{
|
{
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.NeedsRefreshInternal(item, providerInfo);
|
return base.NeedsRefreshInternal(item, providerInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the comparison data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <returns>Guid.</returns>
|
||||||
|
private Guid GetComparisonData(BaseItem item)
|
||||||
|
{
|
||||||
|
var season = (Season)item;
|
||||||
|
var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(seriesId))
|
||||||
|
{
|
||||||
|
// Process images
|
||||||
|
var imagesXmlPath = Path.Combine(RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "banners.xml");
|
||||||
|
|
||||||
|
var imagesFileInfo = new FileInfo(imagesXmlPath);
|
||||||
|
|
||||||
|
return GetComparisonData(imagesFileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Guid.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the comparison data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="imagesFileInfo">The images file info.</param>
|
||||||
|
/// <returns>Guid.</returns>
|
||||||
|
private Guid GetComparisonData(FileInfo imagesFileInfo)
|
||||||
|
{
|
||||||
|
var date = imagesFileInfo.Exists ? imagesFileInfo.LastWriteTimeUtc : DateTime.MinValue;
|
||||||
|
|
||||||
|
var key = date.Ticks + imagesFileInfo.FullName;
|
||||||
|
|
||||||
|
return key.GetMD5();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
|
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -107,162 +184,106 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
|
|
||||||
var season = (Season)item;
|
var season = (Season)item;
|
||||||
|
|
||||||
if (!HasLocalMeta(item))
|
var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null;
|
||||||
{
|
|
||||||
var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null;
|
|
||||||
|
|
||||||
if (seriesId != null)
|
if (!string.IsNullOrEmpty(seriesId))
|
||||||
|
{
|
||||||
|
// Process images
|
||||||
|
var imagesXmlPath = Path.Combine(RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "banners.xml");
|
||||||
|
|
||||||
|
var imagesFileInfo = new FileInfo(imagesXmlPath);
|
||||||
|
|
||||||
|
if (imagesFileInfo.Exists)
|
||||||
{
|
{
|
||||||
var status = await FetchSeasonData(season, seriesId, cancellationToken).ConfigureAwait(false);
|
if (!season.HasImage(ImageType.Primary) || !season.HasImage(ImageType.Banner) || season.BackdropImagePaths.Count == 0)
|
||||||
|
{
|
||||||
|
var xmlDoc = new XmlDocument();
|
||||||
|
xmlDoc.Load(imagesXmlPath);
|
||||||
|
|
||||||
SetLastRefreshed(item, DateTime.UtcNow, status);
|
await FetchImages(season, xmlDoc, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
Logger.Info("Season provider not fetching because series does not have a tvdb id: " + season.Path);
|
|
||||||
}
|
BaseProviderInfo data;
|
||||||
else
|
if (!item.ProviderData.TryGetValue(Id, out data))
|
||||||
{
|
{
|
||||||
Logger.Info("Season provider not fetching because local meta exists: " + season.Name);
|
data = new BaseProviderInfo();
|
||||||
|
item.ProviderData[Id] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Data = GetComparisonData(imagesFileInfo);
|
||||||
|
|
||||||
|
SetLastRefreshed(item, DateTime.UtcNow);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches the season data.
|
/// Fetches the images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="season">The season.</param>
|
/// <param name="season">The season.</param>
|
||||||
/// <param name="seriesId">The series id.</param>
|
/// <param name="images">The images.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task{System.Boolean}.</returns>
|
/// <returns>Task.</returns>
|
||||||
private async Task<ProviderRefreshStatus> FetchSeasonData(Season season, string seriesId, CancellationToken cancellationToken)
|
private async Task FetchImages(Season season, XmlDocument images, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var seasonNumber = TVUtils.GetSeasonNumberFromPath(season.Path) ?? -1;
|
var seasonNumber = season.IndexNumber ?? -1;
|
||||||
|
|
||||||
season.IndexNumber = seasonNumber;
|
if (seasonNumber == -1)
|
||||||
|
|
||||||
if (seasonNumber == 0)
|
|
||||||
{
|
{
|
||||||
season.Name = "Specials";
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var status = ProviderRefreshStatus.Success;
|
if (ConfigurationManager.Configuration.RefreshItemImages || !season.HasImage(ImageType.Primary))
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(seriesId))
|
|
||||||
{
|
{
|
||||||
return status;
|
var n = images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='season'][Season='" + seasonNumber + "'][Language='" + ConfigurationManager.Configuration.PreferredMetadataLanguage + "']") ??
|
||||||
}
|
images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='season'][Season='" + seasonNumber + "'][Language='en']");
|
||||||
|
if (n != null)
|
||||||
if ((season.PrimaryImagePath == null) || (!season.HasImage(ImageType.Banner)) || (season.BackdropImagePaths == null))
|
|
||||||
{
|
|
||||||
var images = new XmlDocument();
|
|
||||||
var url = string.Format("http://www.thetvdb.com/api/" + TVUtils.TvdbApiKey + "/series/{0}/banners.xml", seriesId);
|
|
||||||
|
|
||||||
using (var imgs = await HttpClient.Get(new HttpRequestOptions
|
|
||||||
{
|
{
|
||||||
Url = url,
|
n = n.SelectSingleNode("./BannerPath");
|
||||||
ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
EnableResponseCache = true
|
|
||||||
|
|
||||||
}).ConfigureAwait(false))
|
if (n != null)
|
||||||
{
|
season.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false);
|
||||||
images.Load(imgs);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (images.HasChildNodes)
|
if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && (ConfigurationManager.Configuration.RefreshItemImages || !season.HasImage(ImageType.Banner)))
|
||||||
|
{
|
||||||
|
var n = images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='seasonwide'][Season='" + seasonNumber + "'][Language='" + ConfigurationManager.Configuration.PreferredMetadataLanguage + "']") ??
|
||||||
|
images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='seasonwide'][Season='" + seasonNumber + "'][Language='en']");
|
||||||
|
if (n != null)
|
||||||
{
|
{
|
||||||
if (ConfigurationManager.Configuration.RefreshItemImages || !season.HasLocalImage("folder"))
|
n = n.SelectSingleNode("./BannerPath");
|
||||||
|
if (n != null)
|
||||||
{
|
{
|
||||||
var n = images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='season'][Season='" + seasonNumber + "'][Language='" + ConfigurationManager.Configuration.PreferredMetadataLanguage + "']") ??
|
var bannerImagePath =
|
||||||
images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='season'][Season='" + seasonNumber + "'][Language='en']");
|
await _providerManager.DownloadAndSaveImage(season,
|
||||||
if (n != null)
|
TVUtils.BannerUrl + n.InnerText,
|
||||||
{
|
"banner" +
|
||||||
n = n.SelectSingleNode("./BannerPath");
|
Path.GetExtension(n.InnerText),
|
||||||
|
ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).
|
||||||
|
ConfigureAwait(false);
|
||||||
|
|
||||||
if (n != null)
|
season.SetImage(ImageType.Banner, bannerImagePath);
|
||||||
season.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && (ConfigurationManager.Configuration.RefreshItemImages || !season.HasLocalImage("banner")))
|
|
||||||
{
|
|
||||||
var n = images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='seasonwide'][Season='" + seasonNumber + "'][Language='" + ConfigurationManager.Configuration.PreferredMetadataLanguage + "']") ??
|
|
||||||
images.SelectSingleNode("//Banner[BannerType='season'][BannerType2='seasonwide'][Season='" + seasonNumber + "'][Language='en']");
|
|
||||||
if (n != null)
|
|
||||||
{
|
|
||||||
n = n.SelectSingleNode("./BannerPath");
|
|
||||||
if (n != null)
|
|
||||||
{
|
|
||||||
var bannerImagePath =
|
|
||||||
await _providerManager.DownloadAndSaveImage(season,
|
|
||||||
TVUtils.BannerUrl + n.InnerText,
|
|
||||||
"banner" +
|
|
||||||
Path.GetExtension(n.InnerText),
|
|
||||||
ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).
|
|
||||||
ConfigureAwait(false);
|
|
||||||
|
|
||||||
season.SetImage(ImageType.Banner, bannerImagePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && (ConfigurationManager.Configuration.RefreshItemImages || !season.HasLocalImage("backdrop")))
|
|
||||||
{
|
|
||||||
var n = images.SelectSingleNode("//Banner[BannerType='fanart'][Season='" + seasonNumber + "']");
|
|
||||||
if (n != null)
|
|
||||||
{
|
|
||||||
n = n.SelectSingleNode("./BannerPath");
|
|
||||||
if (n != null)
|
|
||||||
{
|
|
||||||
if (season.BackdropImagePaths == null) season.BackdropImagePaths = new List<string>();
|
|
||||||
season.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "backdrop" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!ConfigurationManager.Configuration.SaveLocalMeta) //if saving local - season will inherit from series
|
|
||||||
{
|
|
||||||
// not necessarily accurate but will give a different bit of art to each season
|
|
||||||
var lst = images.SelectNodes("//Banner[BannerType='fanart']");
|
|
||||||
if (lst != null && lst.Count > 0)
|
|
||||||
{
|
|
||||||
var num = seasonNumber % lst.Count;
|
|
||||||
n = lst[num];
|
|
||||||
n = n.SelectSingleNode("./BannerPath");
|
|
||||||
if (n != null)
|
|
||||||
{
|
|
||||||
if (season.BackdropImagePaths == null)
|
|
||||||
season.BackdropImagePaths = new List<string>();
|
|
||||||
|
|
||||||
season.BackdropImagePaths.Add(
|
|
||||||
await _providerManager.DownloadAndSaveImage(season,
|
|
||||||
TVUtils.BannerUrl +
|
|
||||||
n.InnerText,
|
|
||||||
"backdrop" +
|
|
||||||
Path.GetExtension(
|
|
||||||
n.InnerText),
|
|
||||||
ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken)
|
|
||||||
.ConfigureAwait(false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && (ConfigurationManager.Configuration.RefreshItemImages || season.BackdropImagePaths.Count == 0))
|
||||||
/// Determines whether [has local meta] [the specified item].
|
{
|
||||||
/// </summary>
|
var n = images.SelectSingleNode("//Banner[BannerType='fanart'][Season='" + seasonNumber + "']");
|
||||||
/// <param name="item">The item.</param>
|
if (n != null)
|
||||||
/// <returns><c>true</c> if [has local meta] [the specified item]; otherwise, <c>false</c>.</returns>
|
{
|
||||||
private bool HasLocalMeta(BaseItem item)
|
n = n.SelectSingleNode("./BannerPath");
|
||||||
{
|
if (n != null)
|
||||||
//just folder.jpg/png
|
{
|
||||||
return (item.ResolveArgs.ContainsMetaFileByName("folder.jpg") ||
|
if (season.BackdropImagePaths == null) season.BackdropImagePaths = new List<string>();
|
||||||
item.ResolveArgs.ContainsMetaFileByName("folder.png"));
|
season.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "backdrop" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
@ -6,12 +7,13 @@ using MediaBrowser.Controller.Entities.TV;
|
|||||||
using MediaBrowser.Controller.Extensions;
|
using MediaBrowser.Controller.Extensions;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -25,15 +27,27 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class RemoteSeriesProvider : BaseMetadataProvider, IDisposable
|
class RemoteSeriesProvider : BaseMetadataProvider, IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The _provider manager
|
||||||
|
/// </summary>
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The tv db
|
/// The tv db
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly SemaphoreSlim TvDbResourcePool = new SemaphoreSlim(3, 3);
|
internal readonly SemaphoreSlim TvDbResourcePool = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The current.</value>
|
||||||
internal static RemoteSeriesProvider Current { get; private set; }
|
internal static RemoteSeriesProvider Current { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _zip client
|
||||||
|
/// </summary>
|
||||||
|
private readonly IZipClient _zipClient;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the HTTP client.
|
/// Gets the HTTP client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -47,8 +61,9 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
/// <param name="logManager">The log manager.</param>
|
/// <param name="logManager">The log manager.</param>
|
||||||
/// <param name="configurationManager">The configuration manager.</param>
|
/// <param name="configurationManager">The configuration manager.</param>
|
||||||
/// <param name="providerManager">The provider manager.</param>
|
/// <param name="providerManager">The provider manager.</param>
|
||||||
|
/// <param name="zipClient">The zip client.</param>
|
||||||
/// <exception cref="System.ArgumentNullException">httpClient</exception>
|
/// <exception cref="System.ArgumentNullException">httpClient</exception>
|
||||||
public RemoteSeriesProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
|
public RemoteSeriesProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IZipClient zipClient)
|
||||||
: base(logManager, configurationManager)
|
: base(logManager, configurationManager)
|
||||||
{
|
{
|
||||||
if (httpClient == null)
|
if (httpClient == null)
|
||||||
@ -57,6 +72,7 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
}
|
}
|
||||||
HttpClient = httpClient;
|
HttpClient = httpClient;
|
||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
|
_zipClient = zipClient;
|
||||||
Current = this;
|
Current = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,13 +97,9 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const string SeriesQuery = "GetSeries.php?seriesname={0}";
|
private const string SeriesQuery = "GetSeries.php?seriesname={0}";
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The series get
|
/// The series get zip
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const string SeriesGet = "http://www.thetvdb.com/api/{0}/series/{1}/{2}.xml";
|
private const string SeriesGetZip = "http://www.thetvdb.com/api/{0}/series/{1}/all/{2}.zip";
|
||||||
/// <summary>
|
|
||||||
/// The get actors
|
|
||||||
/// </summary>
|
|
||||||
private const string GetActors = "http://www.thetvdb.com/api/{0}/series/{1}/actors.xml";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The LOCA l_ MET a_ FIL e_ NAME
|
/// The LOCA l_ MET a_ FIL e_ NAME
|
||||||
@ -125,6 +137,30 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether [refresh on version change].
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
|
||||||
|
protected override bool RefreshOnVersionChange
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the provider version.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The provider version.</value>
|
||||||
|
protected override string ProviderVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Needses the refresh internal.
|
/// Needses the refresh internal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -133,9 +169,43 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||||
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
|
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
|
||||||
{
|
{
|
||||||
return !HasLocalMeta(item) && base.NeedsRefreshInternal(item, providerInfo);
|
// Refresh even if local metadata exists because we need episode infos
|
||||||
|
if (GetComparisonData(item) != providerInfo.Data)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.NeedsRefreshInternal(item, providerInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the comparison data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <returns>Guid.</returns>
|
||||||
|
private Guid GetComparisonData(BaseItem item)
|
||||||
|
{
|
||||||
|
var series = (Series)item;
|
||||||
|
var seriesId = series.GetProviderId(MetadataProviders.Tvdb);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(seriesId))
|
||||||
|
{
|
||||||
|
// Process images
|
||||||
|
var path = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId);
|
||||||
|
|
||||||
|
var files = new DirectoryInfo(path)
|
||||||
|
.EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
|
||||||
|
.Select(i => i.FullName + i.LastWriteTimeUtc.Ticks)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (files.Length > 0)
|
||||||
|
{
|
||||||
|
return string.Join(string.Empty, files).GetMD5();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Guid.Empty;
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
|
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -146,30 +216,40 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
|
public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var series = (Series)item;
|
var series = (Series)item;
|
||||||
if (!HasLocalMeta(series))
|
|
||||||
|
var seriesId = series.GetProviderId(MetadataProviders.Tvdb);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(seriesId))
|
||||||
{
|
{
|
||||||
var path = item.Path ?? "";
|
seriesId = await GetSeriesId(series, cancellationToken);
|
||||||
var seriesId = Path.GetFileName(path).GetAttributeValue("tvdbid") ?? await GetSeriesId(series, cancellationToken);
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
var status = ProviderRefreshStatus.Success;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(seriesId))
|
|
||||||
{
|
|
||||||
series.SetProviderId(MetadataProviders.Tvdb, seriesId);
|
|
||||||
|
|
||||||
status = await FetchSeriesData(series, seriesId, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetLastRefreshed(item, DateTime.UtcNow, status);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
Logger.Info("Series provider not fetching because local meta exists or requested to ignore: " + item.Name);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var status = ProviderRefreshStatus.Success;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(seriesId))
|
||||||
|
{
|
||||||
|
series.SetProviderId(MetadataProviders.Tvdb, seriesId);
|
||||||
|
|
||||||
|
var seriesDataPath = GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId);
|
||||||
|
|
||||||
|
status = await FetchSeriesData(series, seriesId, seriesDataPath, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseProviderInfo data;
|
||||||
|
if (!item.ProviderData.TryGetValue(Id, out data))
|
||||||
|
{
|
||||||
|
data = new BaseProviderInfo();
|
||||||
|
item.ProviderData[Id] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Data = GetComparisonData(item);
|
||||||
|
|
||||||
|
SetLastRefreshed(item, DateTime.UtcNow, status);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -177,263 +257,291 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="series">The series.</param>
|
/// <param name="series">The series.</param>
|
||||||
/// <param name="seriesId">The series id.</param>
|
/// <param name="seriesId">The series id.</param>
|
||||||
|
/// <param name="seriesDataPath">The series data path.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task{System.Boolean}.</returns>
|
/// <returns>Task{System.Boolean}.</returns>
|
||||||
private async Task<ProviderRefreshStatus> FetchSeriesData(Series series, string seriesId, CancellationToken cancellationToken)
|
private async Task<ProviderRefreshStatus> FetchSeriesData(Series series, string seriesId, string seriesDataPath, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var status = ProviderRefreshStatus.Success;
|
var status = ProviderRefreshStatus.Success;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(seriesId))
|
var files = Directory.EnumerateFiles(seriesDataPath, "*.xml", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).ToArray();
|
||||||
|
|
||||||
|
var seriesXmlFilename = ConfigurationManager.Configuration.PreferredMetadataLanguage.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, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
string url = string.Format(SeriesGet, TVUtils.TvdbApiKey, seriesId, ConfigurationManager.Configuration.PreferredMetadataLanguage);
|
// Only examine the main info if there's no local metadata
|
||||||
var doc = new XmlDocument();
|
if (!HasLocalMeta(series))
|
||||||
|
{
|
||||||
|
var seriesXmlPath = Path.Combine(seriesDataPath, seriesXmlFilename);
|
||||||
|
var actorsXmlPath = Path.Combine(seriesDataPath, "actors.xml");
|
||||||
|
|
||||||
using (var xml = await HttpClient.Get(new HttpRequestOptions
|
var seriesDoc = new XmlDocument();
|
||||||
|
seriesDoc.Load(seriesXmlPath);
|
||||||
|
|
||||||
|
FetchMainInfo(series, seriesDoc);
|
||||||
|
|
||||||
|
var actorsDoc = new XmlDocument();
|
||||||
|
actorsDoc.Load(actorsXmlPath);
|
||||||
|
|
||||||
|
FetchActors(series, actorsDoc, seriesDoc);
|
||||||
|
|
||||||
|
if (ConfigurationManager.Configuration.SaveLocalMeta)
|
||||||
{
|
{
|
||||||
Url = url,
|
var ms = new MemoryStream();
|
||||||
ResourcePool = TvDbResourcePool,
|
seriesDoc.Save(ms);
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
EnableResponseCache = true
|
|
||||||
|
|
||||||
}).ConfigureAwait(false))
|
await _providerManager.SaveToLibraryFilesystem(series, Path.Combine(series.MetaLocation, LocalMetaFileName), ms, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
|
||||||
doc.Load(xml);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (doc.HasChildNodes)
|
// Process images
|
||||||
{
|
var imagesXmlPath = Path.Combine(seriesDataPath, "banners.xml");
|
||||||
//kick off the actor and image fetch simultaneously
|
|
||||||
var actorTask = FetchActors(series, seriesId, doc, cancellationToken);
|
|
||||||
var imageTask = FetchImages(series, seriesId, cancellationToken);
|
|
||||||
|
|
||||||
series.Name = doc.SafeGetString("//SeriesName");
|
try
|
||||||
series.Overview = doc.SafeGetString("//Overview");
|
{
|
||||||
series.CommunityRating = doc.SafeGetSingle("//Rating", 0, 10);
|
var xmlDoc = new XmlDocument();
|
||||||
series.AirDays = TVUtils.GetAirDays(doc.SafeGetString("//Airs_DayOfWeek"));
|
xmlDoc.Load(imagesXmlPath);
|
||||||
series.AirTime = doc.SafeGetString("//Airs_Time");
|
|
||||||
|
|
||||||
string n = doc.SafeGetString("//banner");
|
await FetchImages(series, xmlDoc, cancellationToken).ConfigureAwait(false);
|
||||||
if (!string.IsNullOrWhiteSpace(n) && !series.HasImage(ImageType.Banner))
|
}
|
||||||
{
|
catch (HttpException)
|
||||||
series.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n, "banner" + Path.GetExtension(n), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false));
|
{
|
||||||
}
|
// Have the provider try again next time, but don't let it fail here
|
||||||
|
status = ProviderRefreshStatus.CompletedWithErrors;
|
||||||
string s = doc.SafeGetString("//Network");
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(s))
|
|
||||||
{
|
|
||||||
series.Studios.Clear();
|
|
||||||
|
|
||||||
foreach (var studio in s.Trim().Split('|'))
|
|
||||||
{
|
|
||||||
series.AddStudio(studio);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
series.OfficialRating = doc.SafeGetString("//ContentRating");
|
|
||||||
|
|
||||||
string g = doc.SafeGetString("//Genre");
|
|
||||||
|
|
||||||
if (g != null)
|
|
||||||
{
|
|
||||||
string[] genres = g.Trim('|').Split('|');
|
|
||||||
if (g.Length > 0)
|
|
||||||
{
|
|
||||||
series.Genres.Clear();
|
|
||||||
|
|
||||||
foreach (var genre in genres)
|
|
||||||
{
|
|
||||||
series.AddGenre(genre);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//wait for other tasks
|
|
||||||
await Task.WhenAll(actorTask, imageTask).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (HttpException)
|
|
||||||
{
|
|
||||||
status = ProviderRefreshStatus.CompletedWithErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ConfigurationManager.Configuration.SaveLocalMeta)
|
|
||||||
{
|
|
||||||
var ms = new MemoryStream();
|
|
||||||
doc.Save(ms);
|
|
||||||
|
|
||||||
await _providerManager.SaveToLibraryFilesystem(series, Path.Combine(series.MetaLocation, LocalMetaFileName), ms, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches the actors.
|
/// Downloads the series zip.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="series">The series.</param>
|
|
||||||
/// <param name="seriesId">The series id.</param>
|
/// <param name="seriesId">The series id.</param>
|
||||||
/// <param name="doc">The doc.</param>
|
/// <param name="seriesDataPath">The series data path.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
private async Task FetchActors(Series series, string seriesId, XmlDocument doc, CancellationToken cancellationToken)
|
internal async Task DownloadSeriesZip(string seriesId, string seriesDataPath, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
string urlActors = string.Format(GetActors, TVUtils.TvdbApiKey, seriesId);
|
var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, ConfigurationManager.Configuration.PreferredMetadataLanguage);
|
||||||
var docActors = new XmlDocument();
|
|
||||||
|
using (var zipStream = await HttpClient.Get(new HttpRequestOptions
|
||||||
using (var actors = await HttpClient.Get(new HttpRequestOptions
|
|
||||||
{
|
{
|
||||||
Url = urlActors,
|
Url = url,
|
||||||
ResourcePool = TvDbResourcePool,
|
ResourcePool = TvDbResourcePool,
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken
|
||||||
EnableResponseCache = true
|
|
||||||
|
|
||||||
}).ConfigureAwait(false))
|
}).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
docActors.Load(actors);
|
// Copy to memory stream because we need a seekable stream
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
await zipStream.CopyToAsync(ms).ConfigureAwait(false);
|
||||||
|
|
||||||
|
ms.Position = 0;
|
||||||
|
_zipClient.ExtractAll(ms, seriesDataPath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the series data path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appPaths">The app paths.</param>
|
||||||
|
/// <param name="seriesId">The series id.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
internal static string GetSeriesDataPath(IApplicationPaths appPaths, string seriesId)
|
||||||
|
{
|
||||||
|
var seriesDataPath = Path.Combine(GetSeriesDataPath(appPaths), seriesId);
|
||||||
|
|
||||||
|
if (!Directory.Exists(seriesDataPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(seriesDataPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (docActors.HasChildNodes)
|
return seriesDataPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the series data path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appPaths">The app paths.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
internal static string GetSeriesDataPath(IApplicationPaths appPaths)
|
||||||
|
{
|
||||||
|
var dataPath = Path.Combine(appPaths.DataPath, "tvdb");
|
||||||
|
|
||||||
|
if (!Directory.Exists(dataPath))
|
||||||
{
|
{
|
||||||
XmlNode actorsNode = null;
|
Directory.CreateDirectory(dataPath);
|
||||||
if (ConfigurationManager.Configuration.SaveLocalMeta)
|
}
|
||||||
|
|
||||||
|
return dataPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches the main info.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="series">The series.</param>
|
||||||
|
/// <param name="doc">The doc.</param>
|
||||||
|
private void FetchMainInfo(Series series, XmlDocument doc)
|
||||||
|
{
|
||||||
|
series.Name = doc.SafeGetString("//SeriesName");
|
||||||
|
series.Overview = doc.SafeGetString("//Overview");
|
||||||
|
series.CommunityRating = doc.SafeGetSingle("//Rating", 0, 10);
|
||||||
|
series.AirDays = TVUtils.GetAirDays(doc.SafeGetString("//Airs_DayOfWeek"));
|
||||||
|
series.AirTime = doc.SafeGetString("//Airs_Time");
|
||||||
|
|
||||||
|
string s = doc.SafeGetString("//Network");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(s))
|
||||||
|
{
|
||||||
|
series.Studios.Clear();
|
||||||
|
|
||||||
|
foreach (var studio in s.Trim().Split('|'))
|
||||||
{
|
{
|
||||||
//add to the main doc for saving
|
series.AddStudio(studio);
|
||||||
var seriesNode = doc.SelectSingleNode("//Series");
|
|
||||||
if (seriesNode != null)
|
|
||||||
{
|
|
||||||
actorsNode = doc.CreateNode(XmlNodeType.Element, "Persons", null);
|
|
||||||
seriesNode.AppendChild(actorsNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var xmlNodeList = docActors.SelectNodes("Actors/Actor");
|
series.OfficialRating = doc.SafeGetString("//ContentRating");
|
||||||
|
|
||||||
if (xmlNodeList != null)
|
string g = doc.SafeGetString("//Genre");
|
||||||
|
|
||||||
|
if (g != null)
|
||||||
|
{
|
||||||
|
string[] genres = g.Trim('|').Split('|');
|
||||||
|
if (g.Length > 0)
|
||||||
{
|
{
|
||||||
series.People.Clear();
|
series.Genres.Clear();
|
||||||
|
|
||||||
foreach (XmlNode p in xmlNodeList)
|
foreach (var genre in genres)
|
||||||
{
|
{
|
||||||
string actorName = p.SafeGetString("Name");
|
series.AddGenre(genre);
|
||||||
string actorRole = p.SafeGetString("Role");
|
|
||||||
if (!string.IsNullOrWhiteSpace(actorName))
|
|
||||||
{
|
|
||||||
series.AddPerson(new PersonInfo { Type = PersonType.Actor, Name = actorName, Role = actorRole });
|
|
||||||
|
|
||||||
if (ConfigurationManager.Configuration.SaveLocalMeta && actorsNode != null)
|
|
||||||
{
|
|
||||||
//create in main doc
|
|
||||||
var personNode = doc.CreateNode(XmlNodeType.Element, "Person", null);
|
|
||||||
foreach (XmlNode subNode in p.ChildNodes)
|
|
||||||
personNode.AppendChild(doc.ImportNode(subNode, true));
|
|
||||||
//need to add the type
|
|
||||||
var typeNode = doc.CreateNode(XmlNodeType.Element, "Type", null);
|
|
||||||
typeNode.InnerText = PersonType.Actor;
|
|
||||||
personNode.AppendChild(typeNode);
|
|
||||||
actorsNode.AppendChild(personNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches the actors.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="series">The series.</param>
|
||||||
|
/// <param name="actorsDoc">The actors doc.</param>
|
||||||
|
/// <param name="seriesDoc">The seriesDoc.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
private void FetchActors(Series series, XmlDocument actorsDoc, XmlDocument seriesDoc)
|
||||||
|
{
|
||||||
|
XmlNode actorsNode = null;
|
||||||
|
if (ConfigurationManager.Configuration.SaveLocalMeta)
|
||||||
|
{
|
||||||
|
//add to the main seriesDoc for saving
|
||||||
|
var seriesNode = seriesDoc.SelectSingleNode("//Series");
|
||||||
|
if (seriesNode != null)
|
||||||
|
{
|
||||||
|
actorsNode = seriesDoc.CreateNode(XmlNodeType.Element, "Persons", null);
|
||||||
|
seriesNode.AppendChild(actorsNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var xmlNodeList = actorsDoc.SelectNodes("Actors/Actor");
|
||||||
|
|
||||||
|
if (xmlNodeList != null)
|
||||||
|
{
|
||||||
|
series.People.Clear();
|
||||||
|
|
||||||
|
foreach (XmlNode p in xmlNodeList)
|
||||||
|
{
|
||||||
|
string actorName = p.SafeGetString("Name");
|
||||||
|
string actorRole = p.SafeGetString("Role");
|
||||||
|
if (!string.IsNullOrWhiteSpace(actorName))
|
||||||
|
{
|
||||||
|
series.AddPerson(new PersonInfo { Type = PersonType.Actor, Name = actorName, Role = actorRole });
|
||||||
|
|
||||||
|
if (ConfigurationManager.Configuration.SaveLocalMeta && actorsNode != null)
|
||||||
|
{
|
||||||
|
//create in main seriesDoc
|
||||||
|
var personNode = seriesDoc.CreateNode(XmlNodeType.Element, "Person", null);
|
||||||
|
foreach (XmlNode subNode in p.ChildNodes)
|
||||||
|
personNode.AppendChild(seriesDoc.ImportNode(subNode, true));
|
||||||
|
//need to add the type
|
||||||
|
var typeNode = seriesDoc.CreateNode(XmlNodeType.Element, "Type", null);
|
||||||
|
typeNode.InnerText = PersonType.Actor;
|
||||||
|
personNode.AppendChild(typeNode);
|
||||||
|
actorsNode.AppendChild(personNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The us culture
|
||||||
|
/// </summary>
|
||||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches the images.
|
/// Fetches the images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="series">The series.</param>
|
/// <param name="series">The series.</param>
|
||||||
/// <param name="seriesId">The series id.</param>
|
/// <param name="images">The images.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
private async Task FetchImages(Series series, string seriesId, CancellationToken cancellationToken)
|
private async Task FetchImages(Series series, XmlDocument images, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if ((!string.IsNullOrEmpty(seriesId)) && ((series.PrimaryImagePath == null) || (series.BackdropImagePaths == null)))
|
if (ConfigurationManager.Configuration.RefreshItemImages || !series.HasImage(ImageType.Primary))
|
||||||
{
|
{
|
||||||
string url = string.Format("http://www.thetvdb.com/api/" + TVUtils.TvdbApiKey + "/series/{0}/banners.xml", seriesId);
|
var n = images.SelectSingleNode("//Banner[BannerType='poster']");
|
||||||
var images = new XmlDocument();
|
if (n != null)
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
using (var imgs = await HttpClient.Get(new HttpRequestOptions
|
n = n.SelectSingleNode("./BannerPath");
|
||||||
|
if (n != null)
|
||||||
{
|
{
|
||||||
Url = url,
|
series.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false);
|
||||||
ResourcePool = TvDbResourcePool,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
EnableResponseCache = true
|
|
||||||
|
|
||||||
}).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
images.Load(imgs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
}
|
||||||
{
|
|
||||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
|
|
||||||
{
|
|
||||||
// If a series has no images this will produce a 404.
|
|
||||||
// Return gracefully so we don't keep retrying on subsequent scans
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && (ConfigurationManager.Configuration.RefreshItemImages || !series.HasImage(ImageType.Banner)))
|
||||||
|
{
|
||||||
|
var n = images.SelectSingleNode("//Banner[BannerType='series']");
|
||||||
|
if (n != null)
|
||||||
|
{
|
||||||
|
n = n.SelectSingleNode("./BannerPath");
|
||||||
|
if (n != null)
|
||||||
|
{
|
||||||
|
var bannerImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken);
|
||||||
|
|
||||||
|
series.SetImage(ImageType.Banner, bannerImagePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (images.HasChildNodes)
|
if (series.BackdropImagePaths.Count < ConfigurationManager.Configuration.MaxBackdrops)
|
||||||
|
{
|
||||||
|
var bdNo = 0;
|
||||||
|
var xmlNodeList = images.SelectNodes("//Banner[BannerType='fanart']");
|
||||||
|
if (xmlNodeList != null)
|
||||||
{
|
{
|
||||||
if (ConfigurationManager.Configuration.RefreshItemImages || !series.HasLocalImage("folder"))
|
foreach (XmlNode b in xmlNodeList)
|
||||||
{
|
{
|
||||||
var n = images.SelectSingleNode("//Banner[BannerType='poster']");
|
var p = b.SelectSingleNode("./BannerPath");
|
||||||
if (n != null)
|
|
||||||
|
if (p != null)
|
||||||
{
|
{
|
||||||
n = n.SelectSingleNode("./BannerPath");
|
var bdName = "backdrop" + (bdNo > 0 ? bdNo.ToString(UsCulture) : "");
|
||||||
if (n != null)
|
series.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + p.InnerText, bdName + Path.GetExtension(p.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false));
|
||||||
{
|
bdNo++;
|
||||||
series.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (series.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && (ConfigurationManager.Configuration.RefreshItemImages || !series.HasLocalImage("banner")))
|
|
||||||
{
|
|
||||||
var n = images.SelectSingleNode("//Banner[BannerType='series']");
|
|
||||||
if (n != null)
|
|
||||||
{
|
|
||||||
n = n.SelectSingleNode("./BannerPath");
|
|
||||||
if (n != null)
|
|
||||||
{
|
|
||||||
var bannerImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken);
|
|
||||||
|
|
||||||
series.SetImage(ImageType.Banner, bannerImagePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bdNo = 0;
|
|
||||||
var xmlNodeList = images.SelectNodes("//Banner[BannerType='fanart']");
|
|
||||||
if (xmlNodeList != null)
|
|
||||||
foreach (XmlNode b in xmlNodeList)
|
|
||||||
{
|
|
||||||
series.BackdropImagePaths = new List<string>();
|
|
||||||
var p = b.SelectSingleNode("./BannerPath");
|
|
||||||
if (p != null)
|
|
||||||
{
|
|
||||||
var bdName = "backdrop" + (bdNo > 0 ? bdNo.ToString(UsCulture) : "");
|
|
||||||
if (ConfigurationManager.Configuration.RefreshItemImages || !series.HasLocalImage(bdName))
|
|
||||||
{
|
|
||||||
series.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + p.InnerText, bdName + Path.GetExtension(p.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false));
|
|
||||||
}
|
|
||||||
bdNo++;
|
|
||||||
if (bdNo >= ConfigurationManager.Configuration.MaxBackdrops) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -573,6 +681,9 @@ namespace MediaBrowser.Controller.Providers.TV
|
|||||||
return name.Trim();
|
return name.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
|
204
MediaBrowser.Controller/Providers/TV/TvdbPrescanTask.cs
Normal file
204
MediaBrowser.Controller/Providers/TV/TvdbPrescanTask.cs
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Extensions;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.Providers.TV
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class TvdbPrescanTask
|
||||||
|
/// </summary>
|
||||||
|
public class TvdbPrescanTask : ILibraryPrescanTask
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The server time URL
|
||||||
|
/// </summary>
|
||||||
|
private const string ServerTimeUrl = "http://thetvdb.com/api/Updates.php?type=none";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The updates URL
|
||||||
|
/// </summary>
|
||||||
|
private const string UpdatesUrl = "http://thetvdb.com/api/Updates.php?type=all&time={0}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _HTTP client
|
||||||
|
/// </summary>
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
/// <summary>
|
||||||
|
/// The _logger
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
/// <summary>
|
||||||
|
/// The _config
|
||||||
|
/// </summary>
|
||||||
|
private readonly IConfigurationManager _config;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TvdbPrescanTask"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="httpClient">The HTTP client.</param>
|
||||||
|
/// <param name="config">The config.</param>
|
||||||
|
public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IConfigurationManager config)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the specified progress.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var path = RemoteSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
|
||||||
|
|
||||||
|
var timestampFile = Path.Combine(path, "time.txt");
|
||||||
|
|
||||||
|
var timestampFileInfo = new FileInfo(timestampFile);
|
||||||
|
|
||||||
|
// Don't check for tvdb updates anymore frequently than 24 hours
|
||||||
|
if (timestampFileInfo.Exists && (DateTime.UtcNow - timestampFileInfo.LastWriteTimeUtc).TotalDays < 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find out the last time we queried tvdb for updates
|
||||||
|
var lastUpdateTime = timestampFileInfo.Exists ? File.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
|
||||||
|
|
||||||
|
string newUpdateTime;
|
||||||
|
|
||||||
|
var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList();
|
||||||
|
|
||||||
|
// If this is our first time, update all series
|
||||||
|
if (string.IsNullOrEmpty(lastUpdateTime))
|
||||||
|
{
|
||||||
|
// First get tvdb server time
|
||||||
|
using (var stream = await _httpClient.Get(new HttpRequestOptions
|
||||||
|
{
|
||||||
|
Url = ServerTimeUrl,
|
||||||
|
CancellationToken = cancellationToken,
|
||||||
|
EnableHttpCompression = true,
|
||||||
|
ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool
|
||||||
|
|
||||||
|
}).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var doc = new XmlDocument();
|
||||||
|
|
||||||
|
doc.Load(stream);
|
||||||
|
|
||||||
|
newUpdateTime = doc.SafeGetString("//Time");
|
||||||
|
}
|
||||||
|
|
||||||
|
await UpdateSeries(existingDirectories, path, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var seriesToUpdate = await GetSeriesIdsToUpdate(existingDirectories, lastUpdateTime, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
newUpdateTime = seriesToUpdate.Item2;
|
||||||
|
|
||||||
|
await UpdateSeries(seriesToUpdate.Item1, path, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the series ids to update.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="existingSeriesIds">The existing series ids.</param>
|
||||||
|
/// <param name="lastUpdateTime">The last update time.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task{IEnumerable{System.String}}.</returns>
|
||||||
|
private async Task<Tuple<IEnumerable<string>, string>> GetSeriesIdsToUpdate(IEnumerable<string> existingSeriesIds, string lastUpdateTime, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// First get last time
|
||||||
|
using (var stream = await _httpClient.Get(new HttpRequestOptions
|
||||||
|
{
|
||||||
|
Url = string.Format(UpdatesUrl, lastUpdateTime),
|
||||||
|
CancellationToken = cancellationToken,
|
||||||
|
EnableHttpCompression = true,
|
||||||
|
ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool
|
||||||
|
|
||||||
|
}).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
var doc = new XmlDocument();
|
||||||
|
|
||||||
|
doc.Load(stream);
|
||||||
|
|
||||||
|
var newUpdateTime = doc.SafeGetString("//Time");
|
||||||
|
|
||||||
|
var seriesNodes = doc.SelectNodes("//Series");
|
||||||
|
|
||||||
|
var seriesList = seriesNodes == null ? new string[] { } :
|
||||||
|
seriesNodes.Cast<XmlNode>()
|
||||||
|
.Select(i => i.InnerText)
|
||||||
|
.Where(i => !string.IsNullOrWhiteSpace(i) && existingSeriesIds.Contains(i, StringComparer.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
return new Tuple<IEnumerable<string>, string>(seriesList, newUpdateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the series.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seriesIds">The series ids.</param>
|
||||||
|
/// <param name="seriesDataPath">The series data path.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
private async Task UpdateSeries(IEnumerable<string> seriesIds, string seriesDataPath, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
foreach (var seriesId in seriesIds)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await UpdateSeries(seriesId, seriesDataPath, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
// Already logged at lower levels, but don't fail the whole operation, unless timed out
|
||||||
|
|
||||||
|
if (ex.IsTimedOut)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the series.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The id.</param>
|
||||||
|
/// <param name="seriesDataPath">The series data path.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
private Task UpdateSeries(string id, string seriesDataPath, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_logger.Info("Updating series " + id);
|
||||||
|
|
||||||
|
seriesDataPath = Path.Combine(seriesDataPath, id);
|
||||||
|
|
||||||
|
if (!Directory.Exists(seriesDataPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(seriesDataPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RemoteSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,8 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class LibraryManager : ILibraryManager
|
public class LibraryManager : ILibraryManager
|
||||||
{
|
{
|
||||||
|
private IEnumerable<ILibraryPrescanTask> PrescanTasks { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the intro providers.
|
/// Gets the intro providers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -161,13 +163,15 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||||||
IEnumerable<IVirtualFolderCreator> pluginFolders,
|
IEnumerable<IVirtualFolderCreator> pluginFolders,
|
||||||
IEnumerable<IItemResolver> resolvers,
|
IEnumerable<IItemResolver> resolvers,
|
||||||
IEnumerable<IIntroProvider> introProviders,
|
IEnumerable<IIntroProvider> introProviders,
|
||||||
IEnumerable<IBaseItemComparer> itemComparers)
|
IEnumerable<IBaseItemComparer> itemComparers,
|
||||||
|
IEnumerable<ILibraryPrescanTask> prescanTasks)
|
||||||
{
|
{
|
||||||
EntityResolutionIgnoreRules = rules;
|
EntityResolutionIgnoreRules = rules;
|
||||||
PluginFolderCreators = pluginFolders;
|
PluginFolderCreators = pluginFolders;
|
||||||
EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
|
EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
|
||||||
IntroProviders = introProviders;
|
IntroProviders = introProviders;
|
||||||
Comparers = itemComparers;
|
Comparers = itemComparers;
|
||||||
|
PrescanTasks = prescanTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -841,6 +845,19 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||||||
await ValidateCollectionFolders(folder, cancellationToken).ConfigureAwait(false);
|
await ValidateCollectionFolders(folder, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run prescan tasks
|
||||||
|
foreach (var task in PrescanTasks)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await task.Run(new Progress<double>(), cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error running prescan task", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var innerProgress = new ActionableProgress<double>();
|
var innerProgress = new ActionableProgress<double>();
|
||||||
|
|
||||||
innerProgress.RegisterAction(pct => progress.Report(pct * .8));
|
innerProgress.RegisterAction(pct => progress.Report(pct * .8));
|
||||||
|
@ -18,41 +18,54 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
|||||||
/// <returns>Episode.</returns>
|
/// <returns>Episode.</returns>
|
||||||
protected override Episode Resolve(ItemResolveArgs args)
|
protected override Episode Resolve(ItemResolveArgs args)
|
||||||
{
|
{
|
||||||
var isInSeason = args.Parent is Season;
|
var season = args.Parent as Season;
|
||||||
|
|
||||||
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
|
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
|
||||||
if (isInSeason || args.Parent is Series)
|
if (season != null || args.Parent is Series)
|
||||||
{
|
{
|
||||||
|
Episode episode = null;
|
||||||
|
|
||||||
if (args.IsDirectory)
|
if (args.IsDirectory)
|
||||||
{
|
{
|
||||||
if (args.ContainsFileSystemEntryByName("video_ts"))
|
if (args.ContainsFileSystemEntryByName("video_ts"))
|
||||||
{
|
{
|
||||||
return new Episode
|
episode = new Episode
|
||||||
{
|
{
|
||||||
IndexNumber = TVUtils.GetEpisodeNumberFromFile(args.Path, isInSeason),
|
|
||||||
Path = args.Path,
|
Path = args.Path,
|
||||||
VideoType = VideoType.Dvd
|
VideoType = VideoType.Dvd
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (args.ContainsFileSystemEntryByName("bdmv"))
|
if (args.ContainsFileSystemEntryByName("bdmv"))
|
||||||
{
|
{
|
||||||
return new Episode
|
episode = new Episode
|
||||||
{
|
{
|
||||||
IndexNumber = TVUtils.GetEpisodeNumberFromFile(args.Path, isInSeason),
|
|
||||||
Path = args.Path,
|
Path = args.Path,
|
||||||
VideoType = VideoType.BluRay
|
VideoType = VideoType.BluRay
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var episide = base.Resolve(args);
|
if (episode == null)
|
||||||
|
|
||||||
if (episide != null)
|
|
||||||
{
|
{
|
||||||
episide.IndexNumber = TVUtils.GetEpisodeNumberFromFile(args.Path, isInSeason);
|
episode = base.Resolve(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
return episide;
|
if (episode != null)
|
||||||
|
{
|
||||||
|
episode.IndexNumber = TVUtils.GetEpisodeNumberFromFile(args.Path, season != null);
|
||||||
|
|
||||||
|
if (season != null)
|
||||||
|
{
|
||||||
|
episode.ParentIndexNumber = season.IndexNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (episode.ParentIndexNumber == null)
|
||||||
|
{
|
||||||
|
episode.ParentIndexNumber = TVUtils.GetSeasonNumberFromEpisodeFile(args.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return episode;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -367,7 +367,12 @@ namespace MediaBrowser.ServerApplication
|
|||||||
|
|
||||||
Parallel.Invoke(
|
Parallel.Invoke(
|
||||||
|
|
||||||
() => LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(), GetExports<IVirtualFolderCreator>(), GetExports<IItemResolver>(), GetExports<IIntroProvider>(), GetExports<IBaseItemComparer>()),
|
() => LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(),
|
||||||
|
GetExports<IVirtualFolderCreator>(),
|
||||||
|
GetExports<IItemResolver>(),
|
||||||
|
GetExports<IIntroProvider>(),
|
||||||
|
GetExports<IBaseItemComparer>(),
|
||||||
|
GetExports<ILibraryPrescanTask>()),
|
||||||
|
|
||||||
() => ProviderManager.AddMetadataProviders(GetExports<BaseMetadataProvider>().ToArray())
|
() => ProviderManager.AddMetadataProviders(GetExports<BaseMetadataProvider>().ToArray())
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user