소스 검색

persist provider results

Luke Pulverenti 11 년 전
부모
커밋
81d5e9f808

+ 6 - 1
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -364,11 +364,16 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
+        private string _forcedSortName;
         /// <summary>
         /// Gets or sets the name of the forced sort.
         /// </summary>
         /// <value>The name of the forced sort.</value>
-        public string ForcedSortName { get; set; }
+        public string ForcedSortName
+        {
+            get { return _forcedSortName; }
+            set { _forcedSortName = value; _sortName = null; }
+        }
 
         private string _sortName;
         /// <summary>

+ 3 - 1
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -146,13 +146,15 @@
     <Compile Include="Providers\IDynamicInfoProvider.cs" />
     <Compile Include="Providers\IHasMetadata.cs" />
     <Compile Include="Providers\IImageProvider.cs" />
+    <Compile Include="Providers\IProviderRepository.cs" />
     <Compile Include="Providers\IRemoteImageProvider.cs" />
     <Compile Include="Providers\ILocalImageProvider.cs" />
     <Compile Include="Providers\IMetadataProvider.cs" />
     <Compile Include="Providers\IMetadataService.cs" />
+    <Compile Include="Providers\ItemId.cs" />
     <Compile Include="Providers\MetadataRefreshOptions.cs" />
     <Compile Include="Providers\NameParser.cs" />
-    <Compile Include="Providers\ProviderResult.cs" />
+    <Compile Include="Providers\MetadataStatus.cs" />
     <Compile Include="Session\ISessionManager.cs" />
     <Compile Include="Drawing\ImageExtensions.cs" />
     <Compile Include="Entities\AggregateFolder.cs" />

+ 0 - 17
MediaBrowser.Controller/Persistence/IItemRepository.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
@@ -112,22 +111,6 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken);
-
-        /// <summary>
-        /// Gets the provider history.
-        /// </summary>
-        /// <param name="itemId">The item identifier.</param>
-        /// <returns>IEnumerable{BaseProviderInfo}.</returns>
-        IEnumerable<BaseProviderInfo> GetProviderHistory(Guid itemId);
-
-        /// <summary>
-        /// Saves the provider history.
-        /// </summary>
-        /// <param name="id">The identifier.</param>
-        /// <param name="history">The history.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task SaveProviderHistory(Guid id, IEnumerable<BaseProviderInfo> history, CancellationToken cancellationToken);
     }
 }
 

+ 3 - 25
MediaBrowser.Controller/Providers/IMetadataProvider.cs

@@ -1,6 +1,4 @@
-using MediaBrowser.Model.Entities;
-using System;
-using System.Collections.Generic;
+using System;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -52,21 +50,14 @@ namespace MediaBrowser.Controller.Providers
     public interface IHasChangeMonitor
     {
         /// <summary>
-        /// Determines whether the specified date has changed.
+        /// Determines whether the specified item has changed.
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="date">The date.</param>
-        /// <returns><c>true</c> if the specified date has changed; otherwise, <c>false</c>.</returns>
+        /// <returns><c>true</c> if the specified item has changed; otherwise, <c>false</c>.</returns>
         bool HasChanged(IHasMetadata item, DateTime date);
     }
 
-    public enum MetadataProviderType
-    {
-        Embedded = 0,
-        Local = 1,
-        Remote = 2
-    }
-
     public class MetadataResult<T>
         where T : IHasMetadata
     {
@@ -74,17 +65,4 @@ namespace MediaBrowser.Controller.Providers
         public T Item { get; set; }
     }
 
-    public class ItemId : IHasProviderIds
-    {
-        public string Name { get; set; }
-        public string MetadataLanguage { get; set; }
-        public string MetadataCountryCode { get; set; }
-
-        public Dictionary<string, string> ProviderIds { get; set; }
-
-        public ItemId()
-        {
-            ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-        }
-    }
 }

+ 48 - 0
MediaBrowser.Controller/Providers/IProviderRepository.cs

@@ -0,0 +1,48 @@
+using MediaBrowser.Controller.Persistence;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public interface IProviderRepository : IRepository
+    {
+        /// <summary>
+        /// Gets the provider history.
+        /// </summary>
+        /// <param name="itemId">The item identifier.</param>
+        /// <returns>IEnumerable{BaseProviderInfo}.</returns>
+        IEnumerable<BaseProviderInfo> GetProviderHistory(Guid itemId);
+
+        /// <summary>
+        /// Saves the provider history.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="history">The history.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SaveProviderHistory(Guid id, IEnumerable<BaseProviderInfo> history, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the metadata status.
+        /// </summary>
+        /// <param name="itemId">The item identifier.</param>
+        /// <returns>MetadataStatus.</returns>
+        MetadataStatus GetMetadataStatus(Guid itemId);
+
+        /// <summary>
+        /// Saves the metadata status.
+        /// </summary>
+        /// <param name="status">The status.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SaveMetadataStatus(MetadataStatus status, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Initializes this instance.
+        /// </summary>
+        /// <returns>Task.</returns>
+        Task Initialize();
+    }
+}

+ 35 - 0
MediaBrowser.Controller/Providers/ItemId.cs

@@ -0,0 +1,35 @@
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public class ItemId : IHasProviderIds
+    {
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name { get; set; }
+        /// <summary>
+        /// Gets or sets the metadata language.
+        /// </summary>
+        /// <value>The metadata language.</value>
+        public string MetadataLanguage { get; set; }
+        /// <summary>
+        /// Gets or sets the metadata country code.
+        /// </summary>
+        /// <value>The metadata country code.</value>
+        public string MetadataCountryCode { get; set; }
+        /// <summary>
+        /// Gets or sets the provider ids.
+        /// </summary>
+        /// <value>The provider ids.</value>
+        public Dictionary<string, string> ProviderIds { get; set; }
+
+        public ItemId()
+        {
+            ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+        }
+    }
+}

+ 122 - 0
MediaBrowser.Controller/Providers/MetadataStatus.cs

@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MediaBrowser.Common.Extensions;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public class MetadataStatus
+    {
+        /// <summary>
+        /// Gets or sets the item identifier.
+        /// </summary>
+        /// <value>The item identifier.</value>
+        public Guid ItemId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the date last metadata refresh.
+        /// </summary>
+        /// <value>The date last metadata refresh.</value>
+        public DateTime? DateLastMetadataRefresh { get; set; }
+
+        /// <summary>
+        /// Gets or sets the date last images refresh.
+        /// </summary>
+        /// <value>The date last images refresh.</value>
+        public DateTime? DateLastImagesRefresh { get; set; }
+
+        /// <summary>
+        /// Gets or sets the last result.
+        /// </summary>
+        /// <value>The last result.</value>
+        public ProviderRefreshStatus LastStatus { get; set; }
+
+        /// <summary>
+        /// Gets or sets the last result error message.
+        /// </summary>
+        /// <value>The last result error message.</value>
+        public string LastErrorMessage { get; set; }
+
+        /// <summary>
+        /// Gets or sets the providers refreshed.
+        /// </summary>
+        /// <value>The providers refreshed.</value>
+        public List<Guid> MetadataProvidersRefreshed { get; set; }
+        public List<Guid> ImageProvidersRefreshed { get; set; }
+
+        public void AddStatus(ProviderRefreshStatus status, string errorMessage)
+        {
+            if (LastStatus != status)
+            {
+                IsDirty = true;
+            }
+
+            if (string.IsNullOrEmpty(LastErrorMessage))
+            {
+                LastErrorMessage = errorMessage;
+            }
+            if (LastStatus == ProviderRefreshStatus.Success)
+            {
+                LastStatus = status;
+            }
+        }
+
+        public MetadataStatus()
+        {
+            LastStatus = ProviderRefreshStatus.Success;
+
+            MetadataProvidersRefreshed = new List<Guid>();
+            ImageProvidersRefreshed = new List<Guid>();
+        }
+
+        public bool IsDirty { get; private set; }
+
+        public void SetDateLastMetadataRefresh(DateTime date)
+        {
+            if (date != (DateLastMetadataRefresh ?? DateTime.MinValue))
+            {
+                IsDirty = true;
+            }
+
+            DateLastMetadataRefresh = date;
+        }
+
+        public void SetDateLastImagesRefresh(DateTime date)
+        {
+            if (date != (DateLastImagesRefresh ?? DateTime.MinValue))
+            {
+                IsDirty = true;
+            }
+
+            DateLastImagesRefresh = date;
+        }
+
+        public void AddImageProvidersRefreshed(List<Guid> providerIds)
+        {
+            var count = ImageProvidersRefreshed.Count;
+
+            providerIds.AddRange(ImageProvidersRefreshed);
+
+            ImageProvidersRefreshed = providerIds.Distinct().ToList();
+
+            if (ImageProvidersRefreshed.Count != count)
+            {
+                IsDirty = true;
+            }
+        }
+
+        public void AddMetadataProvidersRefreshed(List<Guid> providerIds)
+        {
+            var count = MetadataProvidersRefreshed.Count;
+
+            providerIds.AddRange(MetadataProvidersRefreshed);
+
+            MetadataProvidersRefreshed = providerIds.Distinct().ToList();
+
+            if (MetadataProvidersRefreshed.Count != count)
+            {
+                IsDirty = true;
+            }
+        }
+    }
+}

+ 0 - 60
MediaBrowser.Controller/Providers/ProviderResult.cs

@@ -1,60 +0,0 @@
-using System;
-
-namespace MediaBrowser.Controller.Providers
-{
-    public class ProviderResult
-    {
-        /// <summary>
-        /// Gets or sets the item identifier.
-        /// </summary>
-        /// <value>The item identifier.</value>
-        public Guid ItemId { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance has refreshed metadata.
-        /// </summary>
-        /// <value><c>true</c> if this instance has refreshed metadata; otherwise, <c>false</c>.</value>
-        public bool HasRefreshedMetadata { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance has refreshed images.
-        /// </summary>
-        /// <value><c>true</c> if this instance has refreshed images; otherwise, <c>false</c>.</value>
-        public bool HasRefreshedImages { get; set; }
-
-        /// <summary>
-        /// Gets or sets the date last refreshed.
-        /// </summary>
-        /// <value>The date last refreshed.</value>
-        public DateTime DateLastRefreshed { get; set; }
-
-        /// <summary>
-        /// Gets or sets the last result.
-        /// </summary>
-        /// <value>The last result.</value>
-        public ProviderRefreshStatus Status { get; set; }
-
-        /// <summary>
-        /// Gets or sets the last result error message.
-        /// </summary>
-        /// <value>The last result error message.</value>
-        public string ErrorMessage { get; set; }
-
-        public void AddStatus(ProviderRefreshStatus status, string errorMessage)
-        {
-            if (string.IsNullOrEmpty(ErrorMessage))
-            {
-                ErrorMessage = errorMessage;
-            }
-            if (Status == ProviderRefreshStatus.Success)
-            {
-                Status = status;
-            }
-        }
-
-        public ProviderResult()
-        {
-            Status = ProviderRefreshStatus.Success;
-        }
-    }
-}

+ 2 - 2
MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs

@@ -15,8 +15,8 @@ namespace MediaBrowser.Providers.GameGenres
     {
         private readonly ILibraryManager _libraryManager;
 
-        public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager)
+        public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo)
         {
             _libraryManager = libraryManager;
         }

+ 2 - 2
MediaBrowser.Providers/Genres/GenreMetadataService.cs

@@ -15,8 +15,8 @@ namespace MediaBrowser.Providers.Genres
     {
         private readonly ILibraryManager _libraryManager;
 
-        public GenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager)
+        public GenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo)
         {
             _libraryManager = libraryManager;
         }

+ 10 - 1
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
@@ -51,16 +52,24 @@ namespace MediaBrowser.Providers.Manager
 
             var providers = GetImageProviders(item, imageProviders).ToList();
 
+            var providerIds = new List<Guid>();
+
             foreach (var provider in providers.OfType<IRemoteImageProvider>())
             {
                 await RefreshFromProvider(item, provider, options, result, cancellationToken).ConfigureAwait(false);
+
+                providerIds.Add(provider.GetType().FullName.GetMD5());
             }
 
             foreach (var provider in providers.OfType<IDynamicImageProvider>())
             {
                 await RefreshFromProvider(item, provider, result, cancellationToken).ConfigureAwait(false);
+
+                providerIds.Add(provider.GetType().FullName.GetMD5());
             }
 
+            result.Providers = providerIds;
+
             return result;
         }
 

+ 29 - 22
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
@@ -18,16 +19,18 @@ namespace MediaBrowser.Providers.Manager
         protected readonly IServerConfigurationManager ServerConfigurationManager;
         protected readonly ILogger Logger;
         protected readonly IProviderManager ProviderManager;
+        private readonly IProviderRepository _providerRepo;
 
         private IMetadataProvider<TItemType>[] _providers = { };
 
         private IImageProvider[] _imageProviders = { };
 
-        protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager)
+        protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo)
         {
             ServerConfigurationManager = serverConfigurationManager;
             Logger = logger;
             ProviderManager = providerManager;
+            _providerRepo = providerRepo;
         }
 
         /// <summary>
@@ -48,9 +51,9 @@ namespace MediaBrowser.Providers.Manager
         /// </summary>
         /// <param name="result">The result.</param>
         /// <returns>Task.</returns>
-        protected Task SaveProviderResult(ProviderResult result)
+        protected Task SaveProviderResult(MetadataStatus result)
         {
-            return Task.FromResult(true);
+            return _providerRepo.SaveMetadataStatus(result, CancellationToken.None);
         }
 
         /// <summary>
@@ -58,12 +61,9 @@ namespace MediaBrowser.Providers.Manager
         /// </summary>
         /// <param name="itemId">The item identifier.</param>
         /// <returns>ProviderResult.</returns>
-        protected ProviderResult GetLastResult(Guid itemId)
+        protected MetadataStatus GetLastResult(Guid itemId)
         {
-            return new ProviderResult
-            {
-                ItemId = itemId
-            };
+            return _providerRepo.GetMetadataStatus(itemId) ?? new MetadataStatus { ItemId = itemId };
         }
 
         public async Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken)
@@ -72,7 +72,9 @@ namespace MediaBrowser.Providers.Manager
 
             var updateType = ItemUpdateType.Unspecified;
             var lastResult = GetLastResult(item.Id);
-            var refreshResult = new ProviderResult { ItemId = item.Id };
+            var refreshResult = lastResult;
+            refreshResult.LastErrorMessage = string.Empty;
+            refreshResult.LastStatus = ProviderRefreshStatus.Success;
 
             var imageProviders = GetImageProviders(item).ToList();
             var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager);
@@ -97,7 +99,7 @@ namespace MediaBrowser.Providers.Manager
             // Next run metadata providers
             if (options.MetadataRefreshMode != MetadataRefreshMode.None)
             {
-                var providers = GetProviders(item, lastResult.HasRefreshedMetadata, options).ToList();
+                var providers = GetProviders(item, lastResult.DateLastMetadataRefresh.HasValue, options).ToList();
 
                 if (providers.Count > 0)
                 {
@@ -105,22 +107,23 @@ namespace MediaBrowser.Providers.Manager
 
                     updateType = updateType | result.UpdateType;
                     refreshResult.AddStatus(result.Status, result.ErrorMessage);
+                    refreshResult.SetDateLastMetadataRefresh(DateTime.UtcNow);
+                    refreshResult.AddImageProvidersRefreshed(result.Providers);
                 }
-
-                refreshResult.HasRefreshedMetadata = true;
             }
 
             // Next run remote image providers, but only if local image providers didn't throw an exception
             if (!localImagesFailed)
             {
-                if ((options.ImageRefreshMode == MetadataRefreshMode.EnsureMetadata && !lastResult.HasRefreshedImages) ||
+                if ((options.ImageRefreshMode == MetadataRefreshMode.EnsureMetadata && !lastResult.DateLastImagesRefresh.HasValue) ||
                                             options.ImageRefreshMode == MetadataRefreshMode.FullRefresh)
                 {
-                    var imagesReult = await itemImageProvider.RefreshImages(itemOfType, imageProviders, options, cancellationToken).ConfigureAwait(false);
+                    var result = await itemImageProvider.RefreshImages(itemOfType, imageProviders, options, cancellationToken).ConfigureAwait(false);
 
-                    updateType = updateType | imagesReult.UpdateType;
-                    refreshResult.AddStatus(imagesReult.Status, imagesReult.ErrorMessage);
-                    refreshResult.HasRefreshedImages = true;
+                    updateType = updateType | result.UpdateType;
+                    refreshResult.AddStatus(result.Status, result.ErrorMessage);
+                    refreshResult.SetDateLastImagesRefresh(DateTime.UtcNow);
+                    refreshResult.AddImageProvidersRefreshed(result.Providers);
                 }
             }
 
@@ -137,9 +140,8 @@ namespace MediaBrowser.Providers.Manager
                 await SaveItem(itemOfType, updateType, cancellationToken);
             }
 
-            if (providersHadChanges)
+            if (providersHadChanges || refreshResult.IsDirty)
             {
-                refreshResult.DateLastRefreshed = DateTime.UtcNow;
                 await SaveProviderResult(refreshResult).ConfigureAwait(false);
             }
         }
@@ -165,7 +167,7 @@ namespace MediaBrowser.Providers.Manager
                 var currentItem = item;
 
                 var providersWithChanges = providers.OfType<IHasChangeMonitor>()
-                    .Where(i => i.HasChanged(currentItem, item.DateLastSaved))
+                    .Where(i => i.HasChanged(currentItem, currentItem.DateLastSaved))
                     .ToList();
 
                 // If local providers are the only ones with changes, then just run those
@@ -219,7 +221,11 @@ namespace MediaBrowser.Providers.Manager
 
         protected virtual async Task<RefreshResult> RefreshWithProviders(TItemType item, MetadataRefreshOptions options, List<IMetadataProvider> providers, CancellationToken cancellationToken)
         {
-            var refreshResult = new RefreshResult { UpdateType = ItemUpdateType.Unspecified };
+            var refreshResult = new RefreshResult
+            {
+                UpdateType = ItemUpdateType.Unspecified,
+                Providers = providers.Select(i => i.GetType().FullName.GetMD5()).ToList()
+            };
 
             var temp = new TItemType();
 
@@ -347,5 +353,6 @@ namespace MediaBrowser.Providers.Manager
         public ItemUpdateType UpdateType { get; set; }
         public ProviderRefreshStatus Status { get; set; }
         public string ErrorMessage { get; set; }
+        public List<Guid> Providers { get; set; }
     }
 }

+ 6 - 6
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -55,7 +55,7 @@ namespace MediaBrowser.Providers.Manager
 
         private readonly IFileSystem _fileSystem;
 
-        private readonly IItemRepository _itemRepo;
+        private readonly IProviderRepository _providerRepo;
 
         private IMetadataService[] _metadataServices = { };
 
@@ -67,15 +67,15 @@ namespace MediaBrowser.Providers.Manager
         /// <param name="libraryMonitor">The directory watchers.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="fileSystem">The file system.</param>
-        /// <param name="itemRepo">The item repo.</param>
-        public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo)
+        /// <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;
-            _itemRepo = itemRepo;
+            _providerRepo = providerRepo;
         }
 
         /// <summary>
@@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Manager
 
             var providerHistories = item.DateLastSaved == default(DateTime) ?
                 new List<BaseProviderInfo>() :
-                _itemRepo.GetProviderHistory(item.Id).ToList();
+                _providerRepo.GetProviderHistory(item.Id).ToList();
 
             // Run the normal providers sequentially in order of priority
             foreach (var provider in MetadataProviders)
@@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Manager
 
             if (result.HasValue || force)
             {
-                await _itemRepo.SaveProviderHistory(item.Id, providerHistories, cancellationToken);
+                await _providerRepo.SaveProviderHistory(item.Id, providerHistories, cancellationToken);
             }
 
             return result;

+ 2 - 2
MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs

@@ -15,8 +15,8 @@ namespace MediaBrowser.Providers.MusicGenres
     {
         private readonly ILibraryManager _libraryManager;
 
-        public MusicGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager)
+        public MusicGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo)
         {
             _libraryManager = libraryManager;
         }

+ 2 - 2
MediaBrowser.Providers/People/PersonMetadataService.cs

@@ -15,8 +15,8 @@ namespace MediaBrowser.Providers.People
     {
         private readonly ILibraryManager _libraryManager;
 
-        public PersonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager)
+        public PersonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo)
         {
             _libraryManager = libraryManager;
         }

+ 2 - 2
MediaBrowser.Providers/Studios/StudioMetadataService.cs

@@ -15,8 +15,8 @@ namespace MediaBrowser.Providers.Studios
     {
         private readonly ILibraryManager _libraryManager;
 
-        public StudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, ILibraryManager libraryManager)
-            : base(serverConfigurationManager, logger, providerManager)
+        public StudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo)
         {
             _libraryManager = libraryManager;
         }

+ 8 - 17
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -56,21 +56,6 @@ namespace MediaBrowser.Server.Implementations.IO
             _tempIgnoredPaths[path] = path;
         }
 
-        /// <summary>
-        /// Removes the temp ignore.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        private async void RemoveTempIgnore(string path)
-        {
-            // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. 
-            // Seeing long delays in some situations, especially over the network.
-            // Seeing delays up to 40 seconds, but not going to ignore changes for that long.
-            await Task.Delay(1500).ConfigureAwait(false);
-
-            string val;
-            _tempIgnoredPaths.TryRemove(path, out val);
-        }
-
         public void ReportFileSystemChangeBeginning(string path)
         {
             if (string.IsNullOrEmpty(path))
@@ -81,14 +66,20 @@ namespace MediaBrowser.Server.Implementations.IO
             TemporarilyIgnore(path);
         }
 
-        public void ReportFileSystemChangeComplete(string path, bool refreshPath)
+        public async void ReportFileSystemChangeComplete(string path, bool refreshPath)
         {
             if (string.IsNullOrEmpty(path))
             {
                 throw new ArgumentNullException("path");
             }
 
-            RemoveTempIgnore(path);
+            // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. 
+            // Seeing long delays in some situations, especially over the network.
+            // Seeing delays up to 40 seconds, but not going to ignore changes for that long.
+            await Task.Delay(1500).ConfigureAwait(false);
+
+            string val;
+            _tempIgnoredPaths.TryRemove(path, out val);
 
             if (refreshPath)
             {

+ 0 - 22
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -58,7 +58,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
         private SqliteChapterRepository _chapterRepository;
         private SqliteMediaStreamsRepository _mediaStreamsRepository;
-        private SqliteProviderInfoRepository _providerInfoRepository;
 
         private IDbCommand _deleteChildrenCommand;
         private IDbCommand _saveChildrenCommand;
@@ -99,10 +98,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
             var mediaStreamsDbFile = Path.Combine(_appPaths.DataPath, "mediainfo.db");
             var mediaStreamsConnection = SqliteExtensions.ConnectToDb(mediaStreamsDbFile, _logger).Result;
             _mediaStreamsRepository = new SqliteMediaStreamsRepository(mediaStreamsConnection, logManager);
-
-            var providerInfosDbFile = Path.Combine(_appPaths.DataPath, "providerinfo.db");
-            var providerInfoConnection = SqliteExtensions.ConnectToDb(providerInfosDbFile, _logger).Result;
-            _providerInfoRepository = new SqliteProviderInfoRepository(providerInfoConnection, logManager);
         }
 
         /// <summary>
@@ -134,7 +129,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
             PrepareStatements();
 
             _mediaStreamsRepository.Initialize();
-            _providerInfoRepository.Initialize();
             _chapterRepository.Initialize();
 
             _shrinkMemoryTimer = new SqliteShrinkMemoryTimer(_connection, _writeLock, _logger);
@@ -436,12 +430,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
                             _mediaStreamsRepository.Dispose();
                             _mediaStreamsRepository = null;
                         }
-
-                        if (_providerInfoRepository != null)
-                        {
-                            _providerInfoRepository.Dispose();
-                            _providerInfoRepository = null;
-                        }
                     }
                 }
                 catch (Exception ex)
@@ -556,15 +544,5 @@ namespace MediaBrowser.Server.Implementations.Persistence
         {
             return _mediaStreamsRepository.SaveMediaStreams(id, streams, cancellationToken);
         }
-
-        public IEnumerable<BaseProviderInfo> GetProviderHistory(Guid itemId)
-        {
-            return _providerInfoRepository.GetBaseProviderInfos(itemId);
-        }
-
-        public Task SaveProviderHistory(Guid id, IEnumerable<BaseProviderInfo> history, CancellationToken cancellationToken)
-        {
-            return _providerInfoRepository.SaveProviderInfos(id, history, cancellationToken);
-        }
     }
 }

+ 190 - 22
MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Controller.Providers;
+using System.IO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System;
 using System.Collections.Generic;
@@ -9,7 +11,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Server.Implementations.Persistence
 {
-    class SqliteProviderInfoRepository
+    public class SqliteProviderInfoRepository : IProviderRepository
     {
         private IDbConnection _connection;
 
@@ -17,32 +19,47 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
         private IDbCommand _deleteInfosCommand;
         private IDbCommand _saveInfoCommand;
+        private IDbCommand _saveStatusCommand;
+        private readonly IApplicationPaths _appPaths;
 
-        public SqliteProviderInfoRepository(IDbConnection connection, ILogManager logManager)
+        public SqliteProviderInfoRepository(IApplicationPaths appPaths, ILogManager logManager)
         {
-            _connection = connection;
-
+            _appPaths = appPaths;
             _logger = logManager.GetLogger(GetType().Name);
         }
 
         private SqliteShrinkMemoryTimer _shrinkMemoryTimer;
-        
+
+        /// <summary>
+        /// Gets the name of the repository
+        /// </summary>
+        /// <value>The name.</value>
+        public string Name
+        {
+            get
+            {
+                return "SQLite";
+            }
+        }
+
         /// <summary>
         /// Opens the connection to the database
         /// </summary>
         /// <returns>Task.</returns>
-        public void Initialize()
+        public async Task Initialize()
         {
-            var createTableCommand
-                = "create table if not exists providerinfos ";
+            var dbFile = Path.Combine(_appPaths.DataPath, "providerinfo.db");
 
-            createTableCommand += "(ItemId GUID, ProviderId GUID, ProviderVersion TEXT, FileStamp GUID, LastRefreshStatus TEXT, LastRefreshed datetime, PRIMARY KEY (ItemId, ProviderId))";
+            _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);
 
             string[] queries = {
 
-                                createTableCommand,
+                                "create table if not exists providerinfos (ItemId GUID, ProviderId GUID, ProviderVersion TEXT, FileStamp GUID, LastRefreshStatus TEXT, LastRefreshed datetime, PRIMARY KEY (ItemId, ProviderId))",
                                 "create index if not exists idx_providerinfos on providerinfos(ItemId, ProviderId)",
 
+                                "create table if not exists MetadataStatus (ItemId GUID PRIMARY KEY, DateLastMetadataRefresh datetime, DateLastImagesRefresh datetime, LastStatus TEXT, LastErrorMessage TEXT, MetadataProvidersRefreshed TEXT, ImageProvidersRefreshed TEXT)",
+                                "create index if not exists idx_MetadataStatus on MetadataStatus(ItemId)",
+
                                 //pragmas
                                 "pragma temp_store = memory",
 
@@ -56,7 +73,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             _shrinkMemoryTimer = new SqliteShrinkMemoryTimer(_connection, _writeLock, _logger);
         }
 
-        private static readonly string[] SaveColumns =
+        private static readonly string[] SaveHistoryColumns =
         {
             "ItemId",
             "ProviderId",
@@ -66,7 +83,18 @@ namespace MediaBrowser.Server.Implementations.Persistence
             "LastRefreshed"
         };
 
-        private readonly string[] _selectColumns = SaveColumns.Skip(1).ToArray();
+        private readonly string[] _historySelectColumns = SaveHistoryColumns.Skip(1).ToArray();
+
+        private static readonly string[] StatusColumns =
+        {
+            "ItemId",
+            "DateLastMetadataRefresh",
+            "DateLastImagesRefresh",
+            "LastStatus",
+            "LastErrorMessage",
+            "MetadataProvidersRefreshed",
+            "ImageProvidersRefreshed"
+        };
 
         /// <summary>
         /// The _write lock
@@ -85,16 +113,27 @@ namespace MediaBrowser.Server.Implementations.Persistence
             _saveInfoCommand = _connection.CreateCommand();
 
             _saveInfoCommand.CommandText = string.Format("replace into providerinfos ({0}) values ({1})",
-                string.Join(",", SaveColumns),
-                string.Join(",", SaveColumns.Select(i => "@" + i).ToArray()));
+                string.Join(",", SaveHistoryColumns),
+                string.Join(",", SaveHistoryColumns.Select(i => "@" + i).ToArray()));
 
-            foreach (var col in SaveColumns)
+            foreach (var col in SaveHistoryColumns)
             {
                 _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@" + col);
             }
+
+            _saveStatusCommand = _connection.CreateCommand();
+
+            _saveStatusCommand.CommandText = string.Format("replace into MetadataStatus ({0}) values ({1})",
+                string.Join(",", StatusColumns),
+                string.Join(",", StatusColumns.Select(i => "@" + i).ToArray()));
+
+            foreach (var col in StatusColumns)
+            {
+                _saveStatusCommand.Parameters.Add(_saveStatusCommand, "@" + col);
+            }
         }
 
-        public IEnumerable<BaseProviderInfo> GetBaseProviderInfos(Guid itemId)
+        public IEnumerable<BaseProviderInfo> GetProviderHistory(Guid itemId)
         {
             if (itemId == Guid.Empty)
             {
@@ -103,7 +142,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
             using (var cmd = _connection.CreateCommand())
             {
-                var cmdText = "select " + string.Join(",", _selectColumns) + " from providerinfos where";
+                var cmdText = "select " + string.Join(",", _historySelectColumns) + " from providerinfos where";
 
                 cmdText += " ItemId=@ItemId";
                 cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = itemId;
@@ -121,10 +160,10 @@ namespace MediaBrowser.Server.Implementations.Persistence
         }
 
         /// <summary>
-        /// Gets the chapter.
+        /// Gets the base provider information.
         /// </summary>
         /// <param name="reader">The reader.</param>
-        /// <returns>ChapterInfo.</returns>
+        /// <returns>BaseProviderInfo.</returns>
         private BaseProviderInfo GetBaseProviderInfo(IDataReader reader)
         {
             var item = new BaseProviderInfo
@@ -144,7 +183,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             return item;
         }
 
-        public async Task SaveProviderInfos(Guid id, IEnumerable<BaseProviderInfo> infos, CancellationToken cancellationToken)
+        public async Task SaveProviderHistory(Guid id, IEnumerable<BaseProviderInfo> infos, CancellationToken cancellationToken)
         {
             if (id == Guid.Empty)
             {
@@ -166,7 +205,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
             {
                 transaction = _connection.BeginTransaction();
 
-                // First delete chapters
                 _deleteInfosCommand.GetParameter(0).Value = id;
 
                 _deleteInfosCommand.Transaction = transaction;
@@ -221,6 +259,136 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
         }
 
+        public MetadataStatus GetMetadataStatus(Guid itemId)
+        {
+            if (itemId == Guid.Empty)
+            {
+                throw new ArgumentNullException("itemId");
+            }
+
+            using (var cmd = _connection.CreateCommand())
+            {
+                var cmdText = "select " + string.Join(",", StatusColumns) + " from MetadataStatus where";
+
+                cmdText += " ItemId=@ItemId";
+                cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = itemId;
+
+                cmd.CommandText = cmdText;
+
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
+                {
+                    while (reader.Read())
+                    {
+                        return GetStatus(reader);
+                    }
+
+                    return null;
+                }
+            }
+        }
+
+        private MetadataStatus GetStatus(IDataReader reader)
+        {
+            var result = new MetadataStatus
+            {
+                ItemId = reader.GetGuid(0)
+            };
+
+            if (!reader.IsDBNull(1))
+            {
+                result.DateLastMetadataRefresh = reader.GetDateTime(1).ToUniversalTime();
+            }
+
+            if (!reader.IsDBNull(2))
+            {
+                result.DateLastImagesRefresh = reader.GetDateTime(2).ToUniversalTime();
+            }
+
+            if (!reader.IsDBNull(3))
+            {
+                result.LastStatus = (ProviderRefreshStatus)Enum.Parse(typeof(ProviderRefreshStatus), reader.GetString(3), true);
+            }
+
+            if (!reader.IsDBNull(4))
+            {
+                result.LastErrorMessage = reader.GetString(4);
+            }
+
+            if (!reader.IsDBNull(5))
+            {
+                result.MetadataProvidersRefreshed = reader.GetString(5).Split('|').Where(i => !string.IsNullOrEmpty(i)).Select(i => new Guid(i)).ToList();
+            }
+
+            if (!reader.IsDBNull(6))
+            {
+                result.ImageProvidersRefreshed = reader.GetString(6).Split('|').Where(i => !string.IsNullOrEmpty(i)).Select(i => new Guid(i)).ToList();
+            }
+
+            return result;
+        }
+
+        public async Task SaveMetadataStatus(MetadataStatus status, CancellationToken cancellationToken)
+        {
+            if (status == null)
+            {
+                throw new ArgumentNullException("status");
+            }
+
+            cancellationToken.ThrowIfCancellationRequested();
+
+            await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            IDbTransaction transaction = null;
+
+            try
+            {
+                transaction = _connection.BeginTransaction();
+                
+                _saveStatusCommand.GetParameter(0).Value = status.ItemId;
+                _saveStatusCommand.GetParameter(1).Value = status.DateLastMetadataRefresh;
+                _saveStatusCommand.GetParameter(2).Value = status.DateLastImagesRefresh;
+                _saveStatusCommand.GetParameter(3).Value = status.LastStatus.ToString();
+                _saveStatusCommand.GetParameter(4).Value = status.LastErrorMessage;
+                _saveStatusCommand.GetParameter(5).Value = string.Join("|", status.MetadataProvidersRefreshed.ToArray());
+                _saveStatusCommand.GetParameter(6).Value = string.Join("|", status.ImageProvidersRefreshed.ToArray());
+
+                _saveStatusCommand.Transaction = transaction;
+
+                _saveStatusCommand.ExecuteNonQuery();
+
+                transaction.Commit();
+            }
+            catch (OperationCanceledException)
+            {
+                if (transaction != null)
+                {
+                    transaction.Rollback();
+                }
+
+                throw;
+            }
+            catch (Exception e)
+            {
+                _logger.ErrorException("Failed to save provider info:", e);
+
+                if (transaction != null)
+                {
+                    transaction.Rollback();
+                }
+
+                throw;
+            }
+            finally
+            {
+                if (transaction != null)
+                {
+                    transaction.Dispose();
+                }
+
+                _writeLock.Release();
+            }
+        }
+
         /// <summary>
         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
         /// </summary>

+ 7 - 2
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -14,7 +14,6 @@ using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.FileOrganization;
-using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Localization;
@@ -173,6 +172,7 @@ namespace MediaBrowser.ServerApplication
         internal IItemRepository ItemRepository { get; set; }
         private INotificationsRepository NotificationsRepository { get; set; }
         private IFileOrganizationRepository FileOrganizationRepository { get; set; }
+        private IProviderRepository ProviderRepository { get; set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ApplicationHost"/> class.
@@ -267,6 +267,9 @@ namespace MediaBrowser.ServerApplication
             ItemRepository = new SqliteItemRepository(ApplicationPaths, JsonSerializer, LogManager);
             RegisterSingleInstance(ItemRepository);
 
+            ProviderRepository = new SqliteProviderInfoRepository(ApplicationPaths, LogManager);
+            RegisterSingleInstance(ProviderRepository);
+
             FileOrganizationRepository = await GetFileOrganizationRepository().ConfigureAwait(false);
             RegisterSingleInstance(FileOrganizationRepository);
 
@@ -279,7 +282,7 @@ namespace MediaBrowser.ServerApplication
             LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager);
             RegisterSingleInstance(LibraryMonitor);
 
-            ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ItemRepository);
+            ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ProviderRepository);
             RegisterSingleInstance(ProviderManager);
 
             RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
@@ -427,6 +430,8 @@ namespace MediaBrowser.ServerApplication
         {
             await ItemRepository.Initialize().ConfigureAwait(false);
 
+            await ProviderRepository.Initialize().ConfigureAwait(false);
+            
             ((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
         }