using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Logging; using System; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Controller.Providers { /// /// Class BaseMetadataProvider /// public abstract class BaseMetadataProvider : IDisposable { /// /// Gets the logger. /// /// The logger. protected ILogger Logger { get; set; } protected ILogManager LogManager { get; set; } /// /// Gets the configuration manager. /// /// The configuration manager. protected IServerConfigurationManager ConfigurationManager { get; private set; } // Cache these since they will be used a lot /// /// The false task result /// protected static readonly Task FalseTaskResult = Task.FromResult(false); /// /// The true task result /// protected static readonly Task TrueTaskResult = Task.FromResult(true); /// /// The _id /// protected Guid _id; /// /// Gets the id. /// /// The id. public virtual Guid Id { get { if (_id == Guid.Empty) _id = GetType().FullName.GetMD5(); return _id; } } /// /// Supportses the specified item. /// /// The item. /// true if XXXX, false otherwise public abstract bool Supports(BaseItem item); /// /// Gets a value indicating whether [requires internet]. /// /// true if [requires internet]; otherwise, false. public virtual bool RequiresInternet { get { return false; } } /// /// Gets the provider version. /// /// The provider version. protected virtual string ProviderVersion { get { return null; } } /// /// Gets a value indicating whether [refresh on version change]. /// /// true if [refresh on version change]; otherwise, false. protected virtual bool RefreshOnVersionChange { get { return false; } } /// /// Determines if this provider is relatively slow and, therefore, should be skipped /// in certain instances. Default is whether or not it requires internet. Can be overridden /// for explicit designation. /// /// true if this instance is slow; otherwise, false. public virtual bool IsSlow { get { return RequiresInternet; } } /// /// Initializes a new instance of the class. /// protected BaseMetadataProvider(ILogManager logManager, IServerConfigurationManager configurationManager) { Logger = logManager.GetLogger(GetType().Name); LogManager = logManager; ConfigurationManager = configurationManager; Initialize(); } /// /// Initializes this instance. /// protected virtual void Initialize() { } /// /// Sets the persisted last refresh date on the item for this provider. /// /// The item. /// The value. /// The provider version. /// The status. /// item protected virtual void SetLastRefreshed(BaseItem item, DateTime value, string providerVersion, ProviderRefreshStatus status = ProviderRefreshStatus.Success) { if (item == null) { throw new ArgumentNullException("item"); } var data = item.ProviderData.GetValueOrDefault(Id, new BaseProviderInfo { ProviderId = Id }); data.LastRefreshed = value; data.LastRefreshStatus = status; data.ProviderVersion = providerVersion; // Save the file system stamp for future comparisons if (RefreshOnFileSystemStampChange) { data.FileSystemStamp = GetCurrentFileSystemStamp(item); } item.ProviderData[Id] = data; } /// /// Sets the last refreshed. /// /// The item. /// The value. /// The status. protected virtual void SetLastRefreshed(BaseItem item, DateTime value, ProviderRefreshStatus status = ProviderRefreshStatus.Success) { SetLastRefreshed(item, value, ProviderVersion, status); } /// /// Returns whether or not this provider should be re-fetched. Default functionality can /// compare a provided date with a last refresh time. This can be overridden for more complex /// determinations. /// /// The item. /// true if XXXX, false otherwise /// public bool NeedsRefresh(BaseItem item) { if (item == null) { throw new ArgumentNullException(); } var providerInfo = item.ProviderData.GetValueOrDefault(Id, new BaseProviderInfo()); return NeedsRefreshInternal(item, providerInfo); } /// /// Needses the refresh internal. /// /// The item. /// The provider info. /// true if XXXX, false otherwise /// protected virtual bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) { if (item == null) { throw new ArgumentNullException("item"); } if (providerInfo == null) { throw new ArgumentNullException("providerInfo"); } if (CompareDate(item) > providerInfo.LastRefreshed) { return true; } if (RefreshOnFileSystemStampChange && HasFileSystemStampChanged(item, providerInfo)) { return true; } if (RefreshOnVersionChange && !string.Equals(ProviderVersion, providerInfo.ProviderVersion)) { return true; } return false; } /// /// Determines if the item's file system stamp has changed from the last time the provider refreshed /// /// The item. /// The provider info. /// true if [has file system stamp changed] [the specified item]; otherwise, false. protected bool HasFileSystemStampChanged(BaseItem item, BaseProviderInfo providerInfo) { return GetCurrentFileSystemStamp(item) != providerInfo.FileSystemStamp; } /// /// Override this to return the date that should be compared to the last refresh date /// to determine if this provider should be re-fetched. /// /// The item. /// DateTime. protected virtual DateTime CompareDate(BaseItem item) { return DateTime.MinValue.AddMinutes(1); // want this to be greater than mindate so new items will refresh } /// /// 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 async Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { if (item == null) { throw new ArgumentNullException(); } cancellationToken.ThrowIfCancellationRequested(); Logger.Info("Running for {0}", item.Path ?? item.Name ?? "--Unknown--"); // This provides the ability to cancel just this one provider var innerCancellationTokenSource = new CancellationTokenSource(); Kernel.Instance.ProviderManager.OnProviderRefreshBeginning(this, item, innerCancellationTokenSource); try { var task = FetchAsyncInternal(item, force, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token); await task.ConfigureAwait(false); if (task.IsFaulted) { // Log the AggregateException if (task.Exception != null) { Logger.ErrorException("AggregateException:", task.Exception); } return false; } return task.Result; } catch (OperationCanceledException ex) { Logger.Info("{0} cancelled for {1}", 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 false; } catch (Exception ex) { Logger.ErrorException("failed refreshing {0}", ex, item.Name); SetLastRefreshed(item, DateTime.UtcNow, ProviderRefreshStatus.Failure); return true; } finally { innerCancellationTokenSource.Dispose(); Kernel.Instance.ProviderManager.OnProviderRefreshCompleted(this, item); } } /// /// 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}. protected abstract Task FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken); /// /// Gets the priority. /// /// The priority. public abstract MetadataProviderPriority Priority { get; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(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) { } /// /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes /// /// true if [refresh on file system stamp change]; otherwise, false. protected virtual bool RefreshOnFileSystemStampChange { get { return false; } } /// /// Determines if the parent's file system stamp should be used for comparison /// /// The item. /// true if XXXX, false otherwise protected virtual bool UseParentFileSystemStamp(BaseItem item) { // True when the current item is just a file return !item.ResolveArgs.IsDirectory; } /// /// Gets the item's current file system stamp /// /// The item. /// Guid. private Guid GetCurrentFileSystemStamp(BaseItem item) { if (UseParentFileSystemStamp(item) && item.Parent != null) { return item.Parent.FileSystemStamp; } return item.FileSystemStamp; } } /// /// Determines when a provider should execute, relative to others /// public enum MetadataProviderPriority { // Run this provider at the beginning /// /// The first /// First = 1, // Run this provider after all first priority providers /// /// The second /// Second = 2, // Run this provider after all second priority providers /// /// The third /// Third = 3, // Run this provider last /// /// The last /// Last = 4 } }