diff options
Diffstat (limited to 'MediaBrowser.Providers/Manager/ProviderManager.cs')
| -rw-r--r-- | MediaBrowser.Providers/Manager/ProviderManager.cs | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs new file mode 100644 index 000000000..3696bd02f --- /dev/null +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -0,0 +1,487 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Providers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Manager +{ + /// <summary> + /// Class ProviderManager + /// </summary> + public class ProviderManager : IProviderManager + { + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + + /// <summary> + /// The _HTTP client + /// </summary> + private readonly IHttpClient _httpClient; + + /// <summary> + /// The _directory watchers + /// </summary> + private readonly ILibraryMonitor _libraryMonitor; + + /// <summary> + /// Gets or sets the configuration manager. + /// </summary> + /// <value>The configuration manager.</value> + private IServerConfigurationManager ConfigurationManager { get; set; } + + /// <summary> + /// Gets the list of currently registered metadata prvoiders + /// </summary> + /// <value>The metadata providers enumerable.</value> + private BaseMetadataProvider[] MetadataProviders { get; set; } + + private IRemoteImageProvider[] RemoteImageProviders { get; set; } + private IImageProvider[] ImageProviders { get; set; } + + private readonly IFileSystem _fileSystem; + + private readonly IProviderRepository _providerRepo; + + private IMetadataService[] _metadataServices = { }; + + /// <summary> + /// Initializes a new instance of the <see cref="ProviderManager" /> class. + /// </summary> + /// <param name="httpClient">The HTTP client.</param> + /// <param name="configurationManager">The configuration manager.</param> + /// <param name="libraryMonitor">The directory watchers.</param> + /// <param name="logManager">The log manager.</param> + /// <param name="fileSystem">The file system.</param> + /// <param name="providerRepo">The provider repo.</param> + public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IProviderRepository providerRepo) + { + _logger = logManager.GetLogger("ProviderManager"); + _httpClient = httpClient; + ConfigurationManager = configurationManager; + _libraryMonitor = libraryMonitor; + _fileSystem = fileSystem; + _providerRepo = providerRepo; + } + + /// <summary> + /// Adds the metadata providers. + /// </summary> + /// <param name="providers">The providers.</param> + /// <param name="imageProviders">The image providers.</param> + /// <param name="metadataServices">The metadata services.</param> + /// <param name="metadataProviders">The metadata providers.</param> + public void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders) + { + MetadataProviders = providers.OrderBy(e => e.Priority).ToArray(); + + ImageProviders = imageProviders.OrderBy(i => i.Order).ToArray(); + RemoteImageProviders = ImageProviders.OfType<IRemoteImageProvider>().ToArray(); + + _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray(); + + var providerList = metadataProviders.ToList(); + foreach (var service in _metadataServices) + { + service.AddParts(providerList, ImageProviders); + } + } + + public Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken) + { + var service = _metadataServices.FirstOrDefault(i => i.CanRefresh(item)); + + if (service != null) + { + return service.RefreshMetadata(item, options, cancellationToken); + } + + return ((BaseItem)item).RefreshMetadataDirect(cancellationToken, options.ForceSave, options.ReplaceAllMetadata); + } + + /// <summary> + /// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="force">if set to <c>true</c> [force].</param> + /// <returns>Task{System.Boolean}.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + public async Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + ItemUpdateType? result = null; + + cancellationToken.ThrowIfCancellationRequested(); + + var enableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders; + + var providerHistories = item.DateLastSaved == default(DateTime) ? + new List<BaseProviderInfo>() : + _providerRepo.GetProviderHistory(item.Id).ToList(); + + // Run the normal providers sequentially in order of priority + foreach (var provider in MetadataProviders) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (!ProviderSupportsItem(provider, item)) + { + continue; + } + + // Skip if internet providers are currently disabled + if (provider.RequiresInternet && !enableInternetProviders) + { + continue; + } + + // Put this check below the await because the needs refresh of the next tier of providers may depend on the previous ones running + // This is the case for the fan art provider which depends on the movie and tv providers having run before them + if (provider.RequiresInternet && item.DontFetchMeta && provider.EnforceDontFetchMetadata) + { + continue; + } + + var providerInfo = providerHistories.FirstOrDefault(i => i.ProviderId == provider.Id); + + if (providerInfo == null) + { + providerInfo = new BaseProviderInfo + { + ProviderId = provider.Id + }; + providerHistories.Add(providerInfo); + } + + try + { + if (!force && !provider.NeedsRefresh(item, providerInfo)) + { + continue; + } + } + catch (Exception ex) + { + _logger.Error("Error determining NeedsRefresh for {0}", ex, item.Path); + } + + var updateType = await FetchAsync(provider, item, providerInfo, force, cancellationToken).ConfigureAwait(false); + + if (updateType.HasValue) + { + if (result.HasValue) + { + result = result.Value | updateType.Value; + } + else + { + result = updateType; + } + } + } + + if (result.HasValue || force) + { + await _providerRepo.SaveProviderHistory(item.Id, providerHistories, cancellationToken); + } + + return result; + } + + /// <summary> + /// Providers the supports item. + /// </summary> + /// <param name="provider">The provider.</param> + /// <param name="item">The item.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> + private bool ProviderSupportsItem(BaseMetadataProvider provider, BaseItem item) + { + try + { + return provider.Supports(item); + } + catch (Exception ex) + { + _logger.ErrorException("{0} failed in Supports for type {1}", ex, provider.GetType().Name, item.GetType().Name); + return false; + } + } + + /// <summary> + /// Fetches metadata and returns true or false indicating if any work that requires persistence was done + /// </summary> + /// <param name="provider">The provider.</param> + /// <param name="item">The item.</param> + /// <param name="providerInfo">The provider information.</param> + /// <param name="force">if set to <c>true</c> [force].</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{System.Boolean}.</returns> + /// <exception cref="System.ArgumentNullException">item</exception> + private async Task<ItemUpdateType?> FetchAsync(BaseMetadataProvider provider, BaseItem item, BaseProviderInfo providerInfo, bool force, CancellationToken cancellationToken) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + // Don't clog up the log with these providers + if (!(provider is IDynamicInfoProvider)) + { + _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--"); + } + + try + { + var changed = await provider.FetchAsync(item, force, providerInfo, cancellationToken).ConfigureAwait(false); + + if (changed) + { + return provider.ItemUpdateType; + } + + return null; + } + catch (OperationCanceledException ex) + { + _logger.Debug("{0} canceled for {1}", provider.GetType().Name, item.Name); + + // If the outer cancellation token is the one that caused the cancellation, throw it + if (cancellationToken.IsCancellationRequested && ex.CancellationToken == cancellationToken) + { + throw; + } + + return null; + } + catch (Exception ex) + { + _logger.ErrorException("{0} failed refreshing {1} {2}", ex, provider.GetType().Name, item.Name, item.Path ?? string.Empty); + + provider.SetLastRefreshed(item, DateTime.UtcNow, providerInfo, ProviderRefreshStatus.Failure); + + return ItemUpdateType.Unspecified; + } + } + + /// <summary> + /// Saves to library filesystem. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="path">The path.</param> + /// <param name="dataToSave">The data to save.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException"></exception> + public async Task SaveToLibraryFilesystem(BaseItem item, string path, Stream dataToSave, CancellationToken cancellationToken) + { + if (item == null) + { + throw new ArgumentNullException(); + } + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException(); + } + if (dataToSave == null) + { + throw new ArgumentNullException(); + } + + if (cancellationToken.IsCancellationRequested) + { + dataToSave.Dispose(); + cancellationToken.ThrowIfCancellationRequested(); + } + + //Tell the watchers to ignore + _libraryMonitor.ReportFileSystemChangeBeginning(path); + + if (dataToSave.CanSeek) + { + dataToSave.Position = 0; + } + + try + { + using (dataToSave) + { + using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + { + await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); + } + } + + // If this is ever used for something other than metadata we can add a file type param + item.ResolveArgs.AddMetadataFile(path); + } + finally + { + //Remove the ignore + _libraryMonitor.ReportFileSystemChangeComplete(path, false); + } + } + + + /// <summary> + /// Saves the image. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="url">The URL.</param> + /// <param name="resourcePool">The resource pool.</param> + /// <param name="type">The type.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task SaveImage(BaseItem item, string url, SemaphoreSlim resourcePool, ImageType type, int? imageIndex, CancellationToken cancellationToken) + { + var response = await _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + ResourcePool = resourcePool, + Url = url + + }).ConfigureAwait(false); + + await SaveImage(item, response.Content, response.ContentType, type, imageIndex, url, cancellationToken) + .ConfigureAwait(false); + } + + /// <summary> + /// Saves the image. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="source">The source.</param> + /// <param name="mimeType">Type of the MIME.</param> + /// <param name="type">The type.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <param name="sourceUrl">The source URL.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken) + { + return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken); + } + + /// <summary> + /// Gets the available remote images. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="providerName">Name of the provider.</param> + /// <param name="type">The type.</param> + /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> + public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, CancellationToken cancellationToken, string providerName = null, ImageType? type = null) + { + var providers = GetRemoteImageProviders(item); + + if (!string.IsNullOrEmpty(providerName)) + { + providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)); + } + + var preferredLanguage = item.GetPreferredMetadataLanguage(); + + var tasks = providers.Select(i => GetImages(item, cancellationToken, i, preferredLanguage, type)); + + var results = await Task.WhenAll(tasks).ConfigureAwait(false); + + return results.SelectMany(i => i); + } + + /// <summary> + /// Gets the images. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="i">The i.</param> + /// <param name="preferredLanguage">The preferred language.</param> + /// <param name="type">The type.</param> + /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns> + private async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken, IRemoteImageProvider i, string preferredLanguage, ImageType? type = null) + { + try + { + if (type.HasValue) + { + var result = await i.GetImages(item, type.Value, cancellationToken).ConfigureAwait(false); + + return FilterImages(result, preferredLanguage); + } + else + { + var result = await i.GetAllImages(item, cancellationToken).ConfigureAwait(false); + return FilterImages(result, preferredLanguage); + } + } + catch (Exception ex) + { + _logger.ErrorException("{0} failed in GetImageInfos for type {1}", ex, i.GetType().Name, item.GetType().Name); + return new List<RemoteImageInfo>(); + } + } + + private IEnumerable<RemoteImageInfo> FilterImages(IEnumerable<RemoteImageInfo> images, string preferredLanguage) + { + if (string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase)) + { + images = images.Where(i => string.IsNullOrEmpty(i.Language) || + string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase)); + } + + return images; + } + + private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item) + { + return RemoteImageProviders.Where(i => + { + try + { + return i.Supports(item); + } + catch (Exception ex) + { + _logger.ErrorException("{0} failed in Supports for type {1}", ex, i.GetType().Name, item.GetType().Name); + return false; + } + + }); + } + + /// <summary> + /// Gets the supported image providers. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>IEnumerable{IImageProvider}.</returns> + public IEnumerable<ImageProviderInfo> GetImageProviderInfo(BaseItem item) + { + return GetRemoteImageProviders(item).Select(i => new ImageProviderInfo + { + Name = i.Name, + Order = i.Order + + }); + } + } +} |
