using MediaBrowser.Common.Configuration; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Savers; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Providers.Movies { /// /// Class MovieDbProvider /// public class MovieDbProvider : BaseMetadataProvider, IDisposable { protected static CultureInfo EnUs = new CultureInfo("en-US"); protected readonly IProviderManager ProviderManager; /// /// The movie db /// internal readonly SemaphoreSlim MovieDbResourcePool = new SemaphoreSlim(1, 1); internal static MovieDbProvider Current { get; private set; } /// /// Gets the json serializer. /// /// The json serializer. protected IJsonSerializer JsonSerializer { get; private set; } /// /// Gets the HTTP client. /// /// The HTTP client. protected IHttpClient HttpClient { get; private set; } private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. /// /// The log manager. /// The configuration manager. /// The json serializer. /// The HTTP client. /// The provider manager. public MovieDbProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, IProviderManager providerManager, IFileSystem fileSystem) : base(logManager, configurationManager) { JsonSerializer = jsonSerializer; HttpClient = httpClient; ProviderManager = providerManager; _fileSystem = fileSystem; Current = this; } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { if (dispose) { MovieDbResourcePool.Dispose(); } } /// /// Gets the priority. /// /// The priority. public override MetadataProviderPriority Priority { get { return MetadataProviderPriority.Third; } } /// /// Supportses the specified item. /// /// The item. /// true if XXXX, false otherwise public override bool Supports(BaseItem item) { var trailer = item as Trailer; if (trailer != null) { return !trailer.IsLocalTrailer; } // Don't support local trailers return item is Movie || item is BoxSet || item is MusicVideo; } /// /// Gets a value indicating whether [requires internet]. /// /// true if [requires internet]; otherwise, false. public override bool RequiresInternet { get { return true; } } protected override bool RefreshOnVersionChange { get { return true; } } protected override string ProviderVersion { get { return "3"; } } /// /// The _TMDB settings task /// private TmdbSettingsResult _tmdbSettings; private readonly SemaphoreSlim _tmdbSettingsSemaphore = new SemaphoreSlim(1, 1); /// /// Gets the TMDB settings. /// /// Task{TmdbSettingsResult}. internal async Task GetTmdbSettings(CancellationToken cancellationToken) { if (_tmdbSettings != null) { return _tmdbSettings; } await _tmdbSettingsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { // Check again in case it got populated while we were waiting. if (_tmdbSettings != null) { return _tmdbSettings; } using (var json = await GetMovieDbResponse(new HttpRequestOptions { Url = string.Format(TmdbConfigUrl, ApiKey), CancellationToken = cancellationToken, AcceptHeader = AcceptHeader }).ConfigureAwait(false)) { _tmdbSettings = JsonSerializer.DeserializeFromStream(json); return _tmdbSettings; } } finally { _tmdbSettingsSemaphore.Release(); } } private const string TmdbConfigUrl = "http://api.themoviedb.org/3/configuration?api_key={0}"; private const string Search3 = @"http://api.themoviedb.org/3/search/{3}?api_key={1}&query={0}&language={2}"; private const string GetMovieInfo3 = @"http://api.themoviedb.org/3/movie/{0}?api_key={1}&append_to_response=casts,releases,images,keywords,trailers"; private const string GetBoxSetInfo3 = @"http://api.themoviedb.org/3/collection/{0}?api_key={1}&append_to_response=images"; internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669"; internal static string AcceptHeader = "application/json,image/*"; protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) { if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb))) { return true; } return base.NeedsRefreshInternal(item, providerInfo); } protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) { var path = GetDataFilePath(item); if (!string.IsNullOrEmpty(path)) { var fileInfo = new FileInfo(path); return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed; } return base.NeedsRefreshBasedOnCompareDate(item, providerInfo); } /// /// Gets the movie data path. /// /// The app paths. /// if set to true [is box set]. /// The TMDB id. /// System.String. internal static string GetMovieDataPath(IApplicationPaths appPaths, bool isBoxSet, string tmdbId) { var dataPath = isBoxSet ? GetBoxSetsDataPath(appPaths) : GetMoviesDataPath(appPaths); return Path.Combine(dataPath, tmdbId); } internal static string GetMoviesDataPath(IApplicationPaths appPaths) { var dataPath = Path.Combine(appPaths.DataPath, "tmdb-movies"); return dataPath; } internal static string GetBoxSetsDataPath(IApplicationPaths appPaths) { var dataPath = Path.Combine(appPaths.DataPath, "tmdb-collections"); return dataPath; } /// /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// /// The item. /// if set to true [force]. /// The cancellation token /// Task{System.Boolean}. public override async Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var id = item.GetProviderId(MetadataProviders.Tmdb); if (string.IsNullOrEmpty(id)) { id = item.GetProviderId(MetadataProviders.Imdb); } // Don't search for music video id's because it is very easy to misidentify. if (string.IsNullOrEmpty(id) && !(item is MusicVideo)) { id = await FindId(item, cancellationToken).ConfigureAwait(false); } if (!string.IsNullOrEmpty(id)) { cancellationToken.ThrowIfCancellationRequested(); await FetchMovieData(item, id, force, cancellationToken).ConfigureAwait(false); } SetLastRefreshed(item, DateTime.UtcNow, providerInfo); return true; } /// /// Determines whether [has alt meta] [the specified item]. /// /// The item. /// true if [has alt meta] [the specified item]; otherwise, false. internal static bool HasAltMeta(BaseItem item) { if (item is BoxSet) { return item.LocationType == LocationType.FileSystem && item.ResolveArgs.ContainsMetaFileByName("collection.xml"); } var path = MovieXmlSaver.GetMovieSavePath(item); if (item.LocationType == LocationType.FileSystem) { // If mixed with multiple movies in one folder, resolve args won't have the file system children return item.ResolveArgs.ContainsMetaFileByName(Path.GetFileName(path)) || File.Exists(path); } return false; } /// /// Finds the id. /// /// The item. /// The cancellation token /// Task{System.String}. public async Task FindId(BaseItem item, CancellationToken cancellationToken) { int? yearInName; string name = item.Name; NameParser.ParseName(name, out name, out yearInName); var year = item.ProductionYear ?? yearInName; Logger.Info("MovieDbProvider: Finding id for item: " + name); var language = item.GetPreferredMetadataLanguage().ToLower(); //if we are a boxset - look at our first child var boxset = item as BoxSet; if (boxset != null) { // See if any movies have a collection id already var collId = boxset.Children.Concat(boxset.GetLinkedChildren()).OfType