commit
e7e7d96f51
@ -0,0 +1,244 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using TvDbSharper;
|
||||
using TvDbSharper.Dto;
|
||||
|
||||
namespace MediaBrowser.Providers.TV.TheTVDB
|
||||
{
|
||||
public class TvDbClientManager
|
||||
{
|
||||
private readonly SemaphoreSlim _cacheWriteLock = new SemaphoreSlim(1, 1);
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly TvDbClient _tvDbClient;
|
||||
private DateTime _tokenCreatedAt;
|
||||
private const string DefaultLanguage = "en";
|
||||
|
||||
public TvDbClientManager(IMemoryCache memoryCache)
|
||||
{
|
||||
_cache = memoryCache;
|
||||
_tvDbClient = new TvDbClient();
|
||||
_tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey);
|
||||
_tokenCreatedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
public TvDbClient TvDbClient
|
||||
{
|
||||
get
|
||||
{
|
||||
// Refresh if necessary
|
||||
if (_tokenCreatedAt > DateTime.Now.Subtract(TimeSpan.FromHours(20)))
|
||||
{
|
||||
try
|
||||
{
|
||||
_tvDbClient.Authentication.RefreshTokenAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey);
|
||||
}
|
||||
|
||||
_tokenCreatedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
return _tvDbClient;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByNameAsync(string name, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey("series", name, language);
|
||||
return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken));
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<Series>> GetSeriesByIdAsync(int tvdbId, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey("series", tvdbId, language);
|
||||
return TryGetValue(cacheKey, language,() => TvDbClient.Series.GetAsync(tvdbId, cancellationToken));
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<EpisodeRecord>> GetEpisodesAsync(int episodeTvdbId, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey("episode", episodeTvdbId, language);
|
||||
return TryGetValue(cacheKey, language,() => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
|
||||
}
|
||||
|
||||
public async Task<List<EpisodeRecord>> GetAllEpisodesAsync(int tvdbId, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Traverse all episode pages and join them together
|
||||
var episodes = new List<EpisodeRecord>();
|
||||
var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), language, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
episodes.AddRange(episodePage.Data);
|
||||
if (!episodePage.Links.Next.HasValue || !episodePage.Links.Last.HasValue)
|
||||
{
|
||||
return episodes;
|
||||
}
|
||||
|
||||
int next = episodePage.Links.Next.Value;
|
||||
int last = episodePage.Links.Last.Value;
|
||||
|
||||
for (var page = next; page <= last; ++page)
|
||||
{
|
||||
episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), language, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
episodes.AddRange(episodePage.Data);
|
||||
}
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByImdbIdAsync(string imdbId, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey("series", imdbId, language);
|
||||
return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken));
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByZap2ItIdAsync(string zap2ItId, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey("series", zap2ItId, language);
|
||||
return TryGetValue( cacheKey, language,() => TvDbClient.Search.SearchSeriesByZap2ItIdAsync(zap2ItId, cancellationToken));
|
||||
}
|
||||
public Task<TvDbResponse<Actor[]>> GetActorsAsync(int tvdbId, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey("actors", tvdbId, language);
|
||||
return TryGetValue(cacheKey, language,() => TvDbClient.Series.GetActorsAsync(tvdbId, cancellationToken));
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<Image[]>> GetImagesAsync(int tvdbId, ImagesQuery imageQuery, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey("images", tvdbId, language, imageQuery);
|
||||
return TryGetValue(cacheKey, language,() => TvDbClient.Series.GetImagesAsync(tvdbId, imageQuery, cancellationToken));
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<Language[]>> GetLanguagesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return TryGetValue("languages", null,() => TvDbClient.Languages.GetAllAsync(cancellationToken));
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<EpisodesSummary>> GetSeriesEpisodeSummaryAsync(int tvdbId, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey("seriesepisodesummary", tvdbId, language);
|
||||
return TryGetValue(cacheKey, language,
|
||||
() => TvDbClient.Series.GetEpisodesSummaryAsync(tvdbId, cancellationToken));
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(int tvdbId, int page, EpisodeQuery episodeQuery,
|
||||
string language, CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheKey = GenerateKey(language, tvdbId, episodeQuery);
|
||||
|
||||
return TryGetValue(cacheKey, language,
|
||||
() => TvDbClient.Series.GetEpisodesAsync(tvdbId, page, episodeQuery, cancellationToken));
|
||||
}
|
||||
|
||||
public Task<string> GetEpisodeTvdbId(EpisodeInfo searchInfo, string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(),
|
||||
out var seriesTvdbId);
|
||||
|
||||
var episodeQuery = new EpisodeQuery();
|
||||
|
||||
// Prefer SxE over premiere date as it is more robust
|
||||
if (searchInfo.IndexNumber.HasValue && searchInfo.ParentIndexNumber.HasValue)
|
||||
{
|
||||
episodeQuery.AiredEpisode = searchInfo.IndexNumber.Value;
|
||||
episodeQuery.AiredSeason = searchInfo.ParentIndexNumber.Value;
|
||||
}
|
||||
else if (searchInfo.PremiereDate.HasValue)
|
||||
{
|
||||
// tvdb expects yyyy-mm-dd format
|
||||
episodeQuery.FirstAired = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<string> GetEpisodeTvdbId(int seriesTvdbId, EpisodeQuery episodeQuery,
|
||||
string language,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var episodePage =
|
||||
await GetEpisodesPageAsync(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return episodePage.Data.FirstOrDefault()?.Id.ToString();
|
||||
}
|
||||
|
||||
public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(int tvdbId, EpisodeQuery episodeQuery,
|
||||
string language, CancellationToken cancellationToken)
|
||||
{
|
||||
return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<T> TryGetValue<T>(string key, string language, Func<Task<T>> resultFactory)
|
||||
{
|
||||
if (_cache.TryGetValue(key, out T cachedValue))
|
||||
{
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
await _cacheWriteLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_cache.TryGetValue(key, out cachedValue))
|
||||
{
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
_tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage;
|
||||
var result = await resultFactory.Invoke().ConfigureAwait(false);
|
||||
_cache.Set(key, result, TimeSpan.FromHours(1));
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cacheWriteLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GenerateKey(params object[] objects)
|
||||
{
|
||||
var key = string.Empty;
|
||||
|
||||
foreach (var obj in objects)
|
||||
{
|
||||
var objType = obj.GetType();
|
||||
if (objType.IsPrimitive || objType == typeof(string))
|
||||
{
|
||||
key += obj + ";";
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (PropertyInfo propertyInfo in objType.GetProperties())
|
||||
{
|
||||
var currentValue = propertyInfo.GetValue(obj, null);
|
||||
if (currentValue == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
key += propertyInfo.Name + "=" + currentValue + ";";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,398 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Providers.TV.TheTVDB
|
||||
{
|
||||
/// <summary>
|
||||
/// Class TvdbPrescanTask
|
||||
/// </summary>
|
||||
public class TvdbPrescanTask : ILibraryPostScanTask
|
||||
{
|
||||
public const string TvdbBaseUrl = "https://thetvdb.com/";
|
||||
|
||||
/// <summary>
|
||||
/// The server time URL
|
||||
/// </summary>
|
||||
private const string ServerTimeUrl = TvdbBaseUrl + "api/Updates.php?type=none";
|
||||
|
||||
/// <summary>
|
||||
/// The updates URL
|
||||
/// </summary>
|
||||
private const string UpdatesUrl = TvdbBaseUrl + "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 IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IXmlReaderSettingsFactory _xmlSettings;
|
||||
|
||||
/// <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, IServerConfigurationManager config, IFileSystem fileSystem, ILibraryManager libraryManager, IXmlReaderSettingsFactory xmlSettings)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_libraryManager = libraryManager;
|
||||
_xmlSettings = xmlSettings;
|
||||
}
|
||||
|
||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <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 = TvdbSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
|
||||
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var timestampFile = Path.Combine(path, "time.txt");
|
||||
|
||||
var timestampFileInfo = _fileSystem.GetFileInfo(timestampFile);
|
||||
|
||||
// Don't check for tvdb updates anymore frequently than 24 hours
|
||||
if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).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 = _fileSystem.GetDirectoryPaths(path)
|
||||
.Select(Path.GetFileName)
|
||||
.ToList();
|
||||
|
||||
var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
Recursive = true,
|
||||
GroupByPresentationUniqueKey = false,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
}
|
||||
|
||||
}).Cast<Series>()
|
||||
.ToList();
|
||||
|
||||
var seriesIdsInLibrary = seriesList
|
||||
.Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
|
||||
.Select(i => i.GetProviderId(MetadataProviders.Tvdb))
|
||||
.ToList();
|
||||
|
||||
var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
var enableInternetProviders = seriesList.Count == 0 ? false : seriesList[0].IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(seriesList[0]), TvdbSeriesProvider.Current.Name);
|
||||
if (!enableInternetProviders)
|
||||
{
|
||||
progress.Report(100);
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is our first time, update all series
|
||||
if (string.IsNullOrEmpty(lastUpdateTime))
|
||||
{
|
||||
// First get tvdb server time
|
||||
using (var response = await _httpClient.SendAsync(new HttpRequestOptions
|
||||
{
|
||||
Url = ServerTimeUrl,
|
||||
CancellationToken = cancellationToken,
|
||||
EnableHttpCompression = true,
|
||||
BufferContent = false
|
||||
|
||||
}, "GET").ConfigureAwait(false))
|
||||
{
|
||||
// First get tvdb server time
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
newUpdateTime = GetUpdateTime(stream);
|
||||
}
|
||||
}
|
||||
|
||||
existingDirectories.AddRange(missingSeries);
|
||||
|
||||
await UpdateSeries(existingDirectories, path, null, progress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var seriesToUpdate = await GetSeriesIdsToUpdate(existingDirectories, lastUpdateTime, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
newUpdateTime = seriesToUpdate.Item2;
|
||||
|
||||
long.TryParse(lastUpdateTime, NumberStyles.Any, UsCulture, out var lastUpdateValue);
|
||||
|
||||
var nullableUpdateValue = lastUpdateValue == 0 ? (long?)null : lastUpdateValue;
|
||||
|
||||
var listToUpdate = seriesToUpdate.Item1.ToList();
|
||||
listToUpdate.AddRange(missingSeries);
|
||||
|
||||
await UpdateSeries(listToUpdate, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the update time.
|
||||
/// </summary>
|
||||
/// <param name="response">The response.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
private string GetUpdateTime(Stream response)
|
||||
{
|
||||
var settings = _xmlSettings.Create(false);
|
||||
|
||||
settings.CheckCharacters = false;
|
||||
settings.IgnoreProcessingInstructions = true;
|
||||
settings.IgnoreComments = true;
|
||||
|
||||
using (var streamReader = new StreamReader(response, Encoding.UTF8))
|
||||
{
|
||||
// Use XmlReader for best performance
|
||||
using (var reader = XmlReader.Create(streamReader, settings))
|
||||
{
|
||||
reader.MoveToContent();
|
||||
reader.Read();
|
||||
|
||||
// Loop through each element
|
||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
switch (reader.Name)
|
||||
{
|
||||
case "Time":
|
||||
{
|
||||
return (reader.ReadElementContentAsString() ?? string.Empty).Trim();
|
||||
}
|
||||
default:
|
||||
reader.Skip();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <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 response = await _httpClient.SendAsync(new HttpRequestOptions
|
||||
{
|
||||
Url = string.Format(UpdatesUrl, lastUpdateTime),
|
||||
CancellationToken = cancellationToken,
|
||||
EnableHttpCompression = true,
|
||||
BufferContent = false
|
||||
|
||||
}, "GET").ConfigureAwait(false))
|
||||
{
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
var data = GetUpdatedSeriesIdList(stream);
|
||||
|
||||
var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var seriesList = data.Item1
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i));
|
||||
|
||||
return new Tuple<IEnumerable<string>, string>(seriesList, data.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Tuple<List<string>, string> GetUpdatedSeriesIdList(Stream stream)
|
||||
{
|
||||
string updateTime = null;
|
||||
var idList = new List<string>();
|
||||
|
||||
var settings = _xmlSettings.Create(false);
|
||||
|
||||
settings.CheckCharacters = false;
|
||||
settings.IgnoreProcessingInstructions = true;
|
||||
settings.IgnoreComments = true;
|
||||
|
||||
using (var streamReader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
// Use XmlReader for best performance
|
||||
using (var reader = XmlReader.Create(streamReader, settings))
|
||||
{
|
||||
reader.MoveToContent();
|
||||
reader.Read();
|
||||
|
||||
// Loop through each element
|
||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
switch (reader.Name)
|
||||
{
|
||||
case "Time":
|
||||
{
|
||||
updateTime = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
|
||||
break;
|
||||
}
|
||||
case "Series":
|
||||
{
|
||||
var id = (reader.ReadElementContentAsString() ?? string.Empty).Trim();
|
||||
idList.Add(id);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
reader.Skip();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Tuple<List<string>, string>(idList, updateTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the series.
|
||||
/// </summary>
|
||||
/// <param name="seriesIds">The series ids.</param>
|
||||
/// <param name="seriesDataPath">The series data path.</param>
|
||||
/// <param name="lastTvDbUpdateTime">The last tv db update time.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task UpdateSeries(List<string> seriesIds, string seriesDataPath, long? lastTvDbUpdateTime, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var numComplete = 0;
|
||||
|
||||
var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
Recursive = true,
|
||||
GroupByPresentationUniqueKey = false,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
}
|
||||
|
||||
}).Cast<Series>();
|
||||
|
||||
// Gather all series into a lookup by tvdb id
|
||||
var allSeries = seriesList
|
||||
.Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
|
||||
.ToLookup(i => i.GetProviderId(MetadataProviders.Tvdb));
|
||||
|
||||
foreach (var seriesId in seriesIds)
|
||||
{
|
||||
// Find the preferred language(s) for the movie in the library
|
||||
var languages = allSeries[seriesId]
|
||||
.Select(i => i.GetPreferredMetadataLanguage())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
foreach (var language in languages)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, language, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating tvdb series id {ID}, language {Language}", seriesId, language);
|
||||
|
||||
// Already logged at lower levels, but don't fail the whole operation, unless timed out
|
||||
// We have to fail this to make it run again otherwise new episode data could potentially be missing
|
||||
if (ex.IsTimedOut)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= seriesIds.Count;
|
||||
percent *= 100;
|
||||
|
||||
progress.Report(percent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the series.
|
||||
/// </summary>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <param name="seriesDataPath">The series data path.</param>
|
||||
/// <param name="lastTvDbUpdateTime">The last tv db update time.</param>
|
||||
/// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Updating series from tvdb " + id + ", language " + preferredMetadataLanguage);
|
||||
|
||||
seriesDataPath = Path.Combine(seriesDataPath, id);
|
||||
|
||||
Directory.CreateDirectory(seriesDataPath);
|
||||
|
||||
return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), null, null, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using MediaBrowser.Model.Entities;
|
||||
namespace MediaBrowser.Providers.TV.TheTVDB
|
||||
{
|
||||
public static class TvdbUtils
|
||||
{
|
||||
public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K";
|
||||
public const string TvdbBaseUrl = "https://www.thetvdb.com/";
|
||||
public const string BannerUrl = TvdbBaseUrl + "banners/";
|
||||
|
||||
public static ImageType GetImageTypeFromKeyType(string keyType)
|
||||
{
|
||||
switch (keyType.ToLowerInvariant())
|
||||
{
|
||||
case "poster":
|
||||
case "season": return ImageType.Primary;
|
||||
case "series":
|
||||
case "seasonwide": return ImageType.Banner;
|
||||
case "fanart": return ImageType.Backdrop;
|
||||
default: throw new ArgumentException($"Invalid or unknown keytype: {keyType}", nameof(keyType));
|
||||
}
|
||||
}
|
||||
|
||||
public static string NormalizeLanguage(string language)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(language))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// pt-br is just pt to tvdb
|
||||
return language.Split('-')[0].ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue