using MediaBrowser.Common.IO ;
using MediaBrowser.Common.Net ;
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Entities.Movies ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.Logging ;
using MediaBrowser.Model.Serialization ;
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 CommonIO ;
namespace MediaBrowser.Providers.Movies
{
public class MovieUpdatesPreScanTask : ILibraryPostScanTask
{
/// <summary>
/// The updates URL
/// </summary>
private const string UpdatesUrl = "http://api.themoviedb.org/3/movie/changes?start_date={0}&api_key={1}&page={2}" ;
/// <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 IJsonSerializer _json ;
private readonly IFileSystem _fileSystem ;
private readonly ILibraryManager _libraryManager ;
/// <summary>
/// Initializes a new instance of the <see cref="MovieUpdatesPreScanTask"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="httpClient">The HTTP client.</param>
/// <param name="config">The config.</param>
/// <param name="json">The json.</param>
public MovieUpdatesPreScanTask ( ILogger logger , IHttpClient httpClient , IServerConfigurationManager config , IJsonSerializer json , IFileSystem fileSystem , ILibraryManager libraryManager )
{
_logger = logger ;
_httpClient = httpClient ;
_config = config ;
_json = json ;
_fileSystem = fileSystem ;
_libraryManager = libraryManager ;
}
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 )
{
if ( ! MovieDbProvider . Current . GetTheMovieDbOptions ( ) . EnableAutomaticUpdates )
{
progress . Report ( 100 ) ;
return ;
}
var path = MovieDbProvider . GetMoviesDataPath ( _config . CommonApplicationPaths ) ;
_fileSystem . CreateDirectory ( path ) ;
var timestampFile = Path . Combine ( path , "time.txt" ) ;
var timestampFileInfo = _fileSystem . GetFileInfo ( timestampFile ) ;
// Don't check for updates every single time
if ( timestampFileInfo . Exists & & ( DateTime . UtcNow - _fileSystem . GetLastWriteTimeUtc ( timestampFileInfo ) ) . TotalDays < 7 )
{
return ;
}
// Find out the last time we queried tvdb for updates
var lastUpdateTime = timestampFileInfo . Exists ? _fileSystem . ReadAllText ( timestampFile , Encoding . UTF8 ) : string . Empty ;
var existingDirectories = Directory . EnumerateDirectories ( path ) . Select ( Path . GetFileName ) . ToList ( ) ;
if ( ! string . IsNullOrEmpty ( lastUpdateTime ) )
{
long lastUpdateTicks ;
if ( long . TryParse ( lastUpdateTime , NumberStyles . Any , UsCulture , out lastUpdateTicks ) )
{
var lastUpdateDate = new DateTime ( lastUpdateTicks , DateTimeKind . Utc ) ;
// They only allow up to 14 days of updates
if ( ( DateTime . UtcNow - lastUpdateDate ) . TotalDays > 13 )
{
lastUpdateDate = DateTime . UtcNow . AddDays ( - 13 ) ;
}
var updatedIds = await GetIdsToUpdate ( lastUpdateDate , 1 , cancellationToken ) . ConfigureAwait ( false ) ;
var existingDictionary = existingDirectories . ToDictionary ( i = > i , StringComparer . OrdinalIgnoreCase ) ;
var idsToUpdate = updatedIds . Where ( i = > ! string . IsNullOrWhiteSpace ( i ) & & existingDictionary . ContainsKey ( i ) ) ;
await UpdateMovies ( idsToUpdate , progress , cancellationToken ) . ConfigureAwait ( false ) ;
}
}
_fileSystem . WriteAllText ( timestampFile , DateTime . UtcNow . Ticks . ToString ( UsCulture ) , Encoding . UTF8 ) ;
progress . Report ( 100 ) ;
}
/// <summary>
/// Gets the ids to update.
/// </summary>
/// <param name="lastUpdateTime">The last update time.</param>
/// <param name="page">The page.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{System.String}}.</returns>
private async Task < IEnumerable < string > > GetIdsToUpdate ( DateTime lastUpdateTime , int page , CancellationToken cancellationToken )
{
bool hasMorePages ;
var list = new List < string > ( ) ;
// First get last time
using ( var stream = await _httpClient . Get ( new HttpRequestOptions
{
Url = string . Format ( UpdatesUrl , lastUpdateTime . ToString ( "yyyy-MM-dd" ) , MovieDbProvider . ApiKey , page ) ,
CancellationToken = cancellationToken ,
EnableHttpCompression = true ,
ResourcePool = MovieDbProvider . Current . MovieDbResourcePool ,
AcceptHeader = MovieDbProvider . AcceptHeader
} ) . ConfigureAwait ( false ) )
{
var obj = _json . DeserializeFromStream < RootObject > ( stream ) ;
var data = obj . results . Select ( i = > i . id . ToString ( UsCulture ) ) ;
list . AddRange ( data ) ;
hasMorePages = page < obj . total_pages ;
}
if ( hasMorePages )
{
var more = await GetIdsToUpdate ( lastUpdateTime , page + 1 , cancellationToken ) . ConfigureAwait ( false ) ;
list . AddRange ( more ) ;
}
return list ;
}
/// <summary>
/// Updates the movies.
/// </summary>
/// <param name="ids">The ids.</param>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
private async Task UpdateMovies ( IEnumerable < string > ids , IProgress < double > progress , CancellationToken cancellationToken )
{
var list = ids . ToList ( ) ;
var numComplete = 0 ;
// Gather all movies into a lookup by tmdb id
var allMovies = _libraryManager . RootFolder
. GetRecursiveChildren ( i = > i is Movie & & ! string . IsNullOrEmpty ( i . GetProviderId ( MetadataProviders . Tmdb ) ) )
. ToLookup ( i = > i . GetProviderId ( MetadataProviders . Tmdb ) ) ;
foreach ( var id in list )
{
// Find the preferred language(s) for the movie in the library
var languages = allMovies [ id ]
. Select ( i = > i . GetPreferredMetadataLanguage ( ) )
. Distinct ( StringComparer . OrdinalIgnoreCase )
. ToList ( ) ;
foreach ( var language in languages )
{
try
{
await UpdateMovie ( id , language , cancellationToken ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error updating tmdb movie id {0}, language {1}" , ex , id , language ) ;
}
}
numComplete + + ;
double percent = numComplete ;
percent / = list . Count ;
percent * = 100 ;
progress . Report ( percent ) ;
}
}
/// <summary>
/// Updates the movie.
/// </summary>
/// <param name="id">The id.</param>
/// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
private Task UpdateMovie ( string id , string preferredMetadataLanguage , CancellationToken cancellationToken )
{
_logger . Info ( "Updating movie from tmdb " + id + ", language " + preferredMetadataLanguage ) ;
return MovieDbProvider . Current . DownloadMovieInfo ( id , preferredMetadataLanguage , cancellationToken ) ;
}
class Result
{
public int id { get ; set ; }
public bool? adult { get ; set ; }
}
class RootObject
{
public List < Result > results { get ; set ; }
public int page { get ; set ; }
public int total_pages { get ; set ; }
public int total_results { get ; set ; }
public RootObject ( )
{
results = new List < Result > ( ) ;
}
}
}
}