aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller/Providers/BaseMetadataProvider.cs')
-rw-r--r--MediaBrowser.Controller/Providers/BaseMetadataProvider.cs512
1 files changed, 408 insertions, 104 deletions
diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
index 50004be44..3916c0f54 100644
--- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
+++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
@@ -1,104 +1,408 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Common.Extensions;
-using System.Threading.Tasks;
-using System;
-
-namespace MediaBrowser.Controller.Providers
-{
- public abstract class BaseMetadataProvider
- {
- protected Guid _id;
- public virtual Guid Id
- {
- get
- {
- if (_id == null) _id = this.GetType().FullName.GetMD5();
- return _id;
- }
- }
-
- public abstract bool Supports(BaseEntity item);
-
- public virtual bool RequiresInternet
- {
- get
- {
- return false;
- }
- }
-
- /// <summary>
- /// Returns the last refresh time of this provider for this item. Providers that care should
- /// call SetLastRefreshed to update this value.
- /// </summary>
- /// <param name="item"></param>
- /// <returns></returns>
- protected virtual DateTime LastRefreshed(BaseEntity item)
- {
- return (item.ProviderData.GetValueOrDefault(this.Id, new BaseProviderInfo())).LastRefreshed;
- }
-
- /// <summary>
- /// Sets the persisted last refresh date on the item for this provider.
- /// </summary>
- /// <param name="item"></param>
- /// <param name="value"></param>
- protected virtual void SetLastRefreshed(BaseEntity item, DateTime value)
- {
- var data = item.ProviderData.GetValueOrDefault(this.Id, new BaseProviderInfo());
- data.LastRefreshed = value;
- item.ProviderData[this.Id] = data;
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- /// <returns></returns>
- public virtual bool NeedsRefresh(BaseEntity item)
- {
- return CompareDate(item) > LastRefreshed(item);
- }
-
- /// <summary>
- /// Override this to return the date that should be compared to the last refresh date
- /// to determine if this provider should be re-fetched.
- /// </summary>
- protected virtual DateTime CompareDate(BaseEntity item)
- {
- return DateTime.MinValue.AddMinutes(1); // want this to be greater than mindate so new items will refresh
- }
-
- public virtual Task FetchIfNeededAsync(BaseEntity item)
- {
- if (this.NeedsRefresh(item))
- return FetchAsync(item, item.ResolveArgs);
- else
- return new Task(() => { });
- }
-
- public abstract Task FetchAsync(BaseEntity item, ItemResolveEventArgs args);
-
- public abstract MetadataProviderPriority Priority { get; }
- }
-
- /// <summary>
- /// Determines when a provider should execute, relative to others
- /// </summary>
- public enum MetadataProviderPriority
- {
- // Run this provider at the beginning
- First = 1,
-
- // Run this provider after all first priority providers
- Second = 2,
-
- // Run this provider after all second priority providers
- Third = 3,
-
- // Run this provider last
- Last = 4
- }
-}
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Logging;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Providers
+{
+ /// <summary>
+ /// Class BaseMetadataProvider
+ /// </summary>
+ public abstract class BaseMetadataProvider : IDisposable
+ {
+ /// <summary>
+ /// Gets the logger.
+ /// </summary>
+ /// <value>The logger.</value>
+ protected ILogger Logger { get; private set; }
+
+ // Cache these since they will be used a lot
+ /// <summary>
+ /// The false task result
+ /// </summary>
+ protected static readonly Task<bool> FalseTaskResult = Task.FromResult(false);
+ /// <summary>
+ /// The true task result
+ /// </summary>
+ protected static readonly Task<bool> TrueTaskResult = Task.FromResult(true);
+
+ /// <summary>
+ /// The _id
+ /// </summary>
+ protected Guid _id;
+ /// <summary>
+ /// Gets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ public virtual Guid Id
+ {
+ get
+ {
+ if (_id == Guid.Empty) _id = GetType().FullName.GetMD5();
+ return _id;
+ }
+ }
+
+ /// <summary>
+ /// Supportses the specified item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ public abstract bool Supports(BaseItem item);
+
+ /// <summary>
+ /// Gets a value indicating whether [requires internet].
+ /// </summary>
+ /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
+ public virtual bool RequiresInternet
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets the provider version.
+ /// </summary>
+ /// <value>The provider version.</value>
+ protected virtual string ProviderVersion
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether [refresh on version change].
+ /// </summary>
+ /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
+ protected virtual bool RefreshOnVersionChange
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <value><c>true</c> if this instance is slow; otherwise, <c>false</c>.</value>
+ public virtual bool IsSlow
+ {
+ get { return RequiresInternet; }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class.
+ /// </summary>
+ protected BaseMetadataProvider()
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Initializes this instance.
+ /// </summary>
+ protected virtual void Initialize()
+ {
+ Logger = LogManager.GetLogger(GetType().Name);
+ }
+
+ /// <summary>
+ /// Sets the persisted last refresh date on the item for this provider.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="value">The value.</param>
+ /// <param name="providerVersion">The provider version.</param>
+ /// <param name="status">The status.</param>
+ /// <exception cref="System.ArgumentNullException">item</exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Sets the last refreshed.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="value">The value.</param>
+ /// <param name="status">The status.</param>
+ protected virtual void SetLastRefreshed(BaseItem item, DateTime value, ProviderRefreshStatus status = ProviderRefreshStatus.Success)
+ {
+ SetLastRefreshed(item, value, ProviderVersion, status);
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ /// <exception cref="System.ArgumentNullException"></exception>
+ public bool NeedsRefresh(BaseItem item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException();
+ }
+
+ var providerInfo = item.ProviderData.GetValueOrDefault(Id, new BaseProviderInfo());
+
+ return NeedsRefreshInternal(item, providerInfo);
+ }
+
+ /// <summary>
+ /// Needses the refresh internal.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="providerInfo">The provider info.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ /// <exception cref="System.ArgumentNullException"></exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Determines if the item's file system stamp has changed from the last time the provider refreshed
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="providerInfo">The provider info.</param>
+ /// <returns><c>true</c> if [has file system stamp changed] [the specified item]; otherwise, <c>false</c>.</returns>
+ protected bool HasFileSystemStampChanged(BaseItem item, BaseProviderInfo providerInfo)
+ {
+ return GetCurrentFileSystemStamp(item) != providerInfo.FileSystemStamp;
+ }
+
+ /// <summary>
+ /// Override this to return the date that should be compared to the last refresh date
+ /// to determine if this provider should be re-fetched.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>DateTime.</returns>
+ protected virtual DateTime CompareDate(BaseItem item)
+ {
+ return DateTime.MinValue.AddMinutes(1); // want this to be greater than mindate so new items will refresh
+ }
+
+ /// <summary>
+ /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
+ /// </summary>
+ /// <param name="item">The item.</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"></exception>
+ public async Task<bool> 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);
+ }
+ }
+
+ /// <summary>
+ /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="force">if set to <c>true</c> [force].</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{System.Boolean}.</returns>
+ protected abstract Task<bool> FetchAsyncInternal(BaseItem item, bool force, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the priority.
+ /// </summary>
+ /// <value>The priority.</value>
+ public abstract MetadataProviderPriority Priority { get; }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool dispose)
+ {
+ }
+
+ /// <summary>
+ /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
+ /// </summary>
+ /// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
+ protected virtual bool RefreshOnFileSystemStampChange
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Determines if the parent's file system stamp should be used for comparison
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ protected virtual bool UseParentFileSystemStamp(BaseItem item)
+ {
+ // True when the current item is just a file
+ return !item.ResolveArgs.IsDirectory;
+ }
+
+ /// <summary>
+ /// Gets the item's current file system stamp
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>Guid.</returns>
+ private Guid GetCurrentFileSystemStamp(BaseItem item)
+ {
+ if (UseParentFileSystemStamp(item) && item.Parent != null)
+ {
+ return item.Parent.FileSystemStamp;
+ }
+
+ return item.FileSystemStamp;
+ }
+ }
+
+ /// <summary>
+ /// Determines when a provider should execute, relative to others
+ /// </summary>
+ public enum MetadataProviderPriority
+ {
+ // Run this provider at the beginning
+ /// <summary>
+ /// The first
+ /// </summary>
+ First = 1,
+
+ // Run this provider after all first priority providers
+ /// <summary>
+ /// The second
+ /// </summary>
+ Second = 2,
+
+ // Run this provider after all second priority providers
+ /// <summary>
+ /// The third
+ /// </summary>
+ Third = 3,
+
+ // Run this provider last
+ /// <summary>
+ /// The last
+ /// </summary>
+ Last = 4
+ }
+}