From ad82c9f5e95e2b1f94ba7adda047dbfbc38004ea Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 28 Jan 2014 13:37:01 -0500 Subject: New provider system. Only for people right now --- MediaBrowser.Controller/Drawing/ImageExtensions.cs | 8 +- MediaBrowser.Controller/Drawing/ImageFormat.cs | 11 ++ MediaBrowser.Controller/Entities/BaseItem.cs | 115 ++++++++++++++++----- MediaBrowser.Controller/Entities/Folder.cs | 13 ++- MediaBrowser.Controller/Entities/IHasImages.cs | 27 ++++- .../Entities/IHasScreenshots.cs | 6 +- MediaBrowser.Controller/Entities/Movies/Movie.cs | 19 ++-- MediaBrowser.Controller/Entities/User.cs | 20 ++-- MediaBrowser.Controller/Entities/Video.cs | 16 +-- MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs | 5 +- .../LiveTv/StreamResponseInfo.cs | 5 +- .../MediaBrowser.Controller.csproj | 8 ++ MediaBrowser.Controller/Providers/IHasMetadata.cs | 31 ++++++ .../Providers/IImageProvider.cs | 28 +---- .../Providers/ILocalImageProvider.cs | 58 +++++++++++ .../Providers/IMetadataProvider.cs | 90 ++++++++++++++++ .../Providers/IMetadataService.cs | 38 +++++++ .../Providers/IProviderManager.cs | 20 +++- .../Providers/IRemoteImageProvider.cs | 48 +++++++++ .../Providers/MetadataRefreshOptions.cs | 49 +++++++++ .../Providers/ProviderResult.cs | 60 +++++++++++ 21 files changed, 585 insertions(+), 90 deletions(-) create mode 100644 MediaBrowser.Controller/Drawing/ImageFormat.cs create mode 100644 MediaBrowser.Controller/Providers/IHasMetadata.cs create mode 100644 MediaBrowser.Controller/Providers/ILocalImageProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IMetadataProvider.cs create mode 100644 MediaBrowser.Controller/Providers/IMetadataService.cs create mode 100644 MediaBrowser.Controller/Providers/IRemoteImageProvider.cs create mode 100644 MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs create mode 100644 MediaBrowser.Controller/Providers/ProviderResult.cs (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/Drawing/ImageExtensions.cs b/MediaBrowser.Controller/Drawing/ImageExtensions.cs index 9dc58b3d2f..c7e1968e7b 100644 --- a/MediaBrowser.Controller/Drawing/ImageExtensions.cs +++ b/MediaBrowser.Controller/Drawing/ImageExtensions.cs @@ -18,17 +18,17 @@ namespace MediaBrowser.Controller.Drawing /// The image. /// To stream. /// The quality. - public static void Save(this Image image, ImageFormat outputFormat, Stream toStream, int quality) + public static void Save(this Image image, System.Drawing.Imaging.ImageFormat outputFormat, Stream toStream, int quality) { // Use special save methods for jpeg and png that will result in a much higher quality image // All other formats use the generic Image.Save - if (ImageFormat.Jpeg.Equals(outputFormat)) + if (System.Drawing.Imaging.ImageFormat.Jpeg.Equals(outputFormat)) { SaveAsJpeg(image, toStream, quality); } - else if (ImageFormat.Png.Equals(outputFormat)) + else if (System.Drawing.Imaging.ImageFormat.Png.Equals(outputFormat)) { - image.Save(toStream, ImageFormat.Png); + image.Save(toStream, System.Drawing.Imaging.ImageFormat.Png); } else { diff --git a/MediaBrowser.Controller/Drawing/ImageFormat.cs b/MediaBrowser.Controller/Drawing/ImageFormat.cs new file mode 100644 index 0000000000..f785625567 --- /dev/null +++ b/MediaBrowser.Controller/Drawing/ImageFormat.cs @@ -0,0 +1,11 @@ + +namespace MediaBrowser.Controller.Drawing +{ + public enum ImageFormat + { + Jpg, + Png, + Gif, + Bmp + } +} diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 7840fb3f07..a50a0f4674 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.Entities /// /// Class BaseItem /// - public abstract class BaseItem : IHasProviderIds, ILibraryItem, IHasImages, IHasUserData + public abstract class BaseItem : IHasProviderIds, ILibraryItem, IHasImages, IHasUserData, IHasMetadata { protected BaseItem() { @@ -767,25 +767,35 @@ namespace MediaBrowser.Controller.Entities }).ToList(); } + public Task RefreshMetadata(CancellationToken cancellationToken, bool resetResolveArgs = true) + { + return RefreshMetadata(new MetadataRefreshOptions { ResetResolveArgs = resetResolveArgs }, cancellationToken); + } + /// /// Overrides the base implementation to refresh metadata for local trailers /// + /// The options. /// The cancellation token. - /// if set to true [is new item]. - /// if set to true [force]. - /// if set to true [allow slow providers]. - /// if set to true [reset resolve args]. /// true if a provider reports we changed - public virtual async Task RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true) + public async Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken) { - if (resetResolveArgs) + if (options.ResetResolveArgs) { // Reload this ResetResolveArgs(); } + await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false); + + return false; + } + + [Obsolete] + public virtual async Task RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) + { // Refresh for the item - var itemRefreshTask = ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh, allowSlowProviders); + var itemRefreshTask = ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh); cancellationToken.ThrowIfCancellationRequested(); @@ -800,15 +810,15 @@ namespace MediaBrowser.Controller.Entities var hasThemeMedia = this as IHasThemeMedia; if (hasThemeMedia != null) { - themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); - themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); } var hasTrailers = this as IHasTrailers; if (hasTrailers != null) { - localTrailersChanged = await RefreshLocalTrailers(hasTrailers, cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + localTrailersChanged = await RefreshLocalTrailers(hasTrailers, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); } } @@ -829,14 +839,20 @@ namespace MediaBrowser.Controller.Entities return changed; } - private async Task RefreshLocalTrailers(IHasTrailers item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true) + private async Task RefreshLocalTrailers(IHasTrailers item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) { var newItems = LoadLocalTrailers().ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds); - var tasks = newItems.Select(i => i.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs: false)); + var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = forceSave, + ReplaceAllMetadata = forceRefresh, + ResetResolveArgs = false + + }, cancellationToken)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); @@ -845,14 +861,20 @@ namespace MediaBrowser.Controller.Entities return itemsChanged || results.Contains(true); } - private async Task RefreshThemeVideos(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true) + private async Task RefreshThemeVideos(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) { var newThemeVideos = LoadThemeVideos().ToList(); var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList(); var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds); - var tasks = newThemeVideos.Select(i => i.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs: false)); + var tasks = newThemeVideos.Select(i => i.RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = forceSave, + ReplaceAllMetadata = forceRefresh, + ResetResolveArgs = false + + }, cancellationToken)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); @@ -864,14 +886,20 @@ namespace MediaBrowser.Controller.Entities /// /// Refreshes the theme songs. /// - private async Task RefreshThemeSongs(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true) + private async Task RefreshThemeSongs(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) { var newThemeSongs = LoadThemeSongs().ToList(); var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList(); var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds); - var tasks = newThemeSongs.Select(i => i.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs: false)); + var tasks = newThemeSongs.Select(i => i.RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = forceSave, + ReplaceAllMetadata = forceRefresh, + ResetResolveArgs = false + + }, cancellationToken)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); @@ -1456,7 +1484,13 @@ namespace MediaBrowser.Controller.Entities // Refresh metadata // Need to disable slow providers or the image might get re-downloaded - return RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false); + return RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = true, + ImageRefreshMode = MetadataRefreshMode.None, + MetadataRefreshMode = MetadataRefreshMode.None + + }, CancellationToken.None); } /// @@ -1482,8 +1516,10 @@ namespace MediaBrowser.Controller.Entities /// /// Validates that images within the item are still on the file system /// - public void ValidateImages() + public bool ValidateImages() { + var changed = false; + // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below var deletedKeys = Images .Where(image => !File.Exists(image.Value)) @@ -1494,14 +1530,28 @@ namespace MediaBrowser.Controller.Entities foreach (var key in deletedKeys) { Images.Remove(key); + changed = true; } + + if (ValidateBackdrops()) + { + changed = true; + } + if (ValidateScreenshots()) + { + changed = true; + } + + return changed; } /// /// Validates that backdrops within the item are still on the file system /// - public void ValidateBackdrops() + private bool ValidateBackdrops() { + var changed = false; + // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below var deletedImages = BackdropImagePaths .Where(path => !File.Exists(path)) @@ -1513,7 +1563,11 @@ namespace MediaBrowser.Controller.Entities BackdropImagePaths.Remove(path); RemoveImageSourceForPath(path); + + changed = true; } + + return changed; } /// @@ -1593,9 +1647,16 @@ namespace MediaBrowser.Controller.Entities /// /// Validates the screenshots. /// - public void ValidateScreenshots() + private bool ValidateScreenshots() { - var hasScreenshots = (IHasScreenshots)this; + var changed = false; + + var hasScreenshots = this as IHasScreenshots; + + if (hasScreenshots == null) + { + return changed; + } // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below var deletedImages = hasScreenshots.ScreenshotImagePaths @@ -1606,7 +1667,10 @@ namespace MediaBrowser.Controller.Entities foreach (var path in deletedImages) { hasScreenshots.ScreenshotImagePaths.Remove(path); + changed = true; } + + return changed; } /// @@ -1699,7 +1763,12 @@ namespace MediaBrowser.Controller.Entities FileSystem.SwapFiles(file1, file2); // Directory watchers should repeat this, but do a quick refresh first - return RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false); + return RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = true, + MetadataRefreshMode = MetadataRefreshMode.None + + }, CancellationToken.None); } public virtual bool IsPlayed(User user) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index a0fefeac77..a4257b2a5b 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MoreLinq; @@ -535,7 +536,13 @@ namespace MediaBrowser.Controller.Entities try { //refresh it - await child.RefreshMetadata(cancellationToken, forceSave: currentTuple.Item2, forceRefresh: forceRefreshMetadata, resetResolveArgs: false).ConfigureAwait(false); + await child.RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = currentTuple.Item2, + ReplaceAllMetadata = forceRefreshMetadata, + ResetResolveArgs = false + + }, cancellationToken).ConfigureAwait(false); } catch (IOException ex) { @@ -907,9 +914,9 @@ namespace MediaBrowser.Controller.Entities return item; } - public override async Task RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true) + public override async Task RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) { - var changed = await base.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs).ConfigureAwait(false); + var changed = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); return (SupportsShortcutChildren && LocationType == LocationType.FileSystem && RefreshLinkedChildren()) || changed; } diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index a7cd76a66a..784337e3bb 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -1,5 +1,6 @@ using MediaBrowser.Model.Entities; using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace MediaBrowser.Controller.Entities @@ -10,7 +11,7 @@ namespace MediaBrowser.Controller.Entities /// Gets the name. /// /// The name. - string Name { get; } + string Name { get; set; } /// /// Gets the path. @@ -24,6 +25,12 @@ namespace MediaBrowser.Controller.Entities /// The identifier. Guid Id { get; } + /// + /// Gets the type of the location. + /// + /// The type of the location. + LocationType LocationType { get; } + /// /// Gets the image path. /// @@ -81,6 +88,24 @@ namespace MediaBrowser.Controller.Entities /// /// System.String. string GetPreferredMetadataLanguage(); + + /// + /// Validates the images and returns true or false indicating if any were removed. + /// + bool ValidateImages(); + + /// + /// Gets or sets the backdrop image paths. + /// + /// The backdrop image paths. + List BackdropImagePaths { get; set; } + + /// + /// Determines whether [contains image with source URL] [the specified URL]. + /// + /// The URL. + /// true if [contains image with source URL] [the specified URL]; otherwise, false. + bool ContainsImageWithSourceUrl(string url); } public static class HasImagesExtensions diff --git a/MediaBrowser.Controller/Entities/IHasScreenshots.cs b/MediaBrowser.Controller/Entities/IHasScreenshots.cs index 2276c707a7..70d154a958 100644 --- a/MediaBrowser.Controller/Entities/IHasScreenshots.cs +++ b/MediaBrowser.Controller/Entities/IHasScreenshots.cs @@ -14,8 +14,10 @@ namespace MediaBrowser.Controller.Entities List ScreenshotImagePaths { get; set; } /// - /// Validates the screenshots. + /// Determines whether [contains image with source URL] [the specified URL]. /// - void ValidateScreenshots(); + /// The URL. + /// true if [contains image with source URL] [the specified URL]; otherwise, false. + bool ContainsImageWithSourceUrl(string url); } } diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 2b252a6c21..dbbe5ce018 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Configuration; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; @@ -108,13 +109,11 @@ namespace MediaBrowser.Controller.Entities.Movies /// The cancellation token. /// if set to true [is new item]. /// if set to true [force]. - /// if set to true [allow slow providers]. - /// The reset resolve args. /// Task{System.Boolean}. - public override async Task RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true) + public override async Task RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) { // Kick off a task to refresh the main item - var result = await base.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs).ConfigureAwait(false); + var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); var specialFeaturesChanged = false; @@ -122,7 +121,7 @@ namespace MediaBrowser.Controller.Entities.Movies // In other words, it must be part of the Parent/Child tree if (LocationType == LocationType.FileSystem && Parent != null && !IsInMixedFolder) { - specialFeaturesChanged = await RefreshSpecialFeatures(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + specialFeaturesChanged = await RefreshSpecialFeatures(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); } return specialFeaturesChanged || result; @@ -135,7 +134,13 @@ namespace MediaBrowser.Controller.Entities.Movies var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds); - var tasks = newItems.Select(i => i.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs: false)); + var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = forceSave, + ReplaceAllMetadata = forceRefresh, + ResetResolveArgs = false + + }, cancellationToken)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 466e709dd0..c109e1d0cd 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Serialization; using System; @@ -212,7 +213,12 @@ namespace MediaBrowser.Controller.Entities // Kick off a task to validate the media library Task.Run(() => ValidateMediaLibrary(new Progress(), CancellationToken.None)); - return RefreshMetadata(CancellationToken.None, forceSave: true, forceRefresh: true); + return RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = true, + ReplaceAllMetadata = true + + }, CancellationToken.None); } /// @@ -275,17 +281,13 @@ namespace MediaBrowser.Controller.Entities /// The cancellation token. /// if set to true [is new item]. /// if set to true [force]. - /// if set to true [allow slow providers]. /// true if a provider reports we changed - public override async Task RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true) + public override async Task RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) { - if (resetResolveArgs) - { - // Reload this - ResetResolveArgs(); - } + // Reload this + ResetResolveArgs(); - var updateReason = await ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh, allowSlowProviders).ConfigureAwait(false); + var updateReason = await ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh).ConfigureAwait(false); var changed = updateReason.HasValue; diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index e663c83538..9c94667669 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using System; @@ -164,13 +165,11 @@ namespace MediaBrowser.Controller.Entities /// The cancellation token. /// if set to true [is new item]. /// if set to true [force]. - /// if set to true [allow slow providers]. - /// The reset resolve args. /// true if a provider reports we changed - public override async Task RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true) + public override async Task RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false) { // Kick off a task to refresh the main item - var result = await base.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs).ConfigureAwait(false); + var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); var additionalPartsChanged = false; @@ -181,7 +180,7 @@ namespace MediaBrowser.Controller.Entities { try { - additionalPartsChanged = await RefreshAdditionalParts(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + additionalPartsChanged = await RefreshAdditionalParts(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false); } catch (IOException ex) { @@ -208,7 +207,12 @@ namespace MediaBrowser.Controller.Entities var itemsChanged = !AdditionalPartIds.SequenceEqual(newItemIds); - var tasks = newItems.Select(i => i.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders)); + var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions + { + ForceSave = forceSave, + ReplaceAllMetadata = forceRefresh + + }, cancellationToken)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs index d9bceb6cad..c94a25a304 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using System.Threading; using System.Threading.Tasks; @@ -11,8 +12,6 @@ namespace MediaBrowser.Controller.LiveTv string MediaType { get; } - LocationType LocationType { get; } - RecordingInfo RecordingInfo { get; set; } string GetClientTypeName(); @@ -21,6 +20,6 @@ namespace MediaBrowser.Controller.LiveTv bool IsParentalAllowed(User user); - Task RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true); + Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs b/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs index c3b438c5ee..b133874d09 100644 --- a/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs +++ b/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs @@ -1,4 +1,5 @@ -using System.IO; +using MediaBrowser.Controller.Drawing; +using System.IO; namespace MediaBrowser.Controller.LiveTv { @@ -14,6 +15,6 @@ namespace MediaBrowser.Controller.LiveTv /// Gets or sets the type of the MIME. /// /// The type of the MIME. - public string MimeType { get; set; } + public ImageFormat Format { get; set; } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 56ac695a2d..ef87c30c78 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -69,6 +69,7 @@ Properties\SharedVersion.cs + @@ -143,8 +144,15 @@ + + + + + + + diff --git a/MediaBrowser.Controller/Providers/IHasMetadata.cs b/MediaBrowser.Controller/Providers/IHasMetadata.cs new file mode 100644 index 0000000000..33c5184b8b --- /dev/null +++ b/MediaBrowser.Controller/Providers/IHasMetadata.cs @@ -0,0 +1,31 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Providers +{ + /// + /// Interface IHasMetadata + /// + public interface IHasMetadata : IHasImages, IHasProviderIds + { + /// + /// Gets the preferred metadata country code. + /// + /// System.String. + string GetPreferredMetadataCountryCode(); + + /// + /// Gets the locked fields. + /// + /// The locked fields. + List LockedFields { get; } + + /// + /// Gets or sets the date last saved. + /// + /// The date last saved. + DateTime DateLastSaved { get; set; } + } +} diff --git a/MediaBrowser.Controller/Providers/IImageProvider.cs b/MediaBrowser.Controller/Providers/IImageProvider.cs index ccf1998445..61f5579f4b 100644 --- a/MediaBrowser.Controller/Providers/IImageProvider.cs +++ b/MediaBrowser.Controller/Providers/IImageProvider.cs @@ -1,9 +1,4 @@ using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Controller.Providers { @@ -26,26 +21,9 @@ namespace MediaBrowser.Controller.Providers bool Supports(IHasImages item); /// - /// Gets the images. + /// Gets the order. /// - /// The item. - /// Type of the image. - /// The cancellation token. - /// Task{IEnumerable{RemoteImageInfo}}. - Task> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken); - - /// - /// Gets the images. - /// - /// The item. - /// The cancellation token. - /// Task{IEnumerable{RemoteImageInfo}}. - Task> GetAllImages(IHasImages item, CancellationToken cancellationToken); - - /// - /// Gets the priority. - /// - /// The priority. - int Priority { get; } + /// The order. + int Order { get; } } } diff --git a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs new file mode 100644 index 0000000000..30f2136059 --- /dev/null +++ b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs @@ -0,0 +1,58 @@ +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Providers +{ + /// + /// This is just a marker interface + /// + public interface ILocalImageProvider : IImageProvider + { + } + + public interface IImageFileProvider : ILocalImageProvider + { + List GetImages(IHasImages item); + } + + public class LocalImageInfo + { + public string Path { get; set; } + public ImageType Type { get; set; } + } + + public interface IDynamicImageProvider : ILocalImageProvider + { + /// + /// Gets the images. + /// + /// The item. + /// List{DynamicImageInfo}. + List GetImageInfos(IHasImages item); + + /// + /// Gets the image. + /// + /// The item. + /// The information. + /// Task{DynamicImageResponse}. + Task GetImage(IHasImages item, DynamicImageInfo info); + } + + public class DynamicImageInfo + { + public string ImageId { get; set; } + public ImageType Type { get; set; } + } + + public class DynamicImageResponse + { + public string Path { get; set; } + public Stream Stream { get; set; } + public ImageFormat Format { get; set; } + } +} diff --git a/MediaBrowser.Controller/Providers/IMetadataProvider.cs b/MediaBrowser.Controller/Providers/IMetadataProvider.cs new file mode 100644 index 0000000000..97249e26d9 --- /dev/null +++ b/MediaBrowser.Controller/Providers/IMetadataProvider.cs @@ -0,0 +1,90 @@ +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Providers +{ + /// + /// Marker interface + /// + public interface IMetadataProvider + { + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + } + + public interface IMetadataProvider : IMetadataProvider + where TItemType : IHasMetadata + { + } + + public interface ILocalMetadataProvider : IMetadataProvider + { + /// + /// Determines whether [has local metadata] [the specified item]. + /// + /// The item. + /// true if [has local metadata] [the specified item]; otherwise, false. + bool HasLocalMetadata(IHasMetadata item); + } + + public interface IRemoteMetadataProvider : IMetadataProvider + { + } + + public interface IRemoteMetadataProvider : IMetadataProvider, IRemoteMetadataProvider + where TItemType : IHasMetadata + { + Task> GetMetadata(ItemId id, CancellationToken cancellationToken); + } + + public interface ILocalMetadataProvider : IMetadataProvider, ILocalMetadataProvider + where TItemType : IHasMetadata + { + Task> GetMetadata(string path, CancellationToken cancellationToken); + } + + public interface IHasChangeMonitor + { + /// + /// Determines whether the specified date has changed. + /// + /// The item. + /// The date. + /// true if the specified date has changed; otherwise, false. + bool HasChanged(IHasMetadata item, DateTime date); + } + + public enum MetadataProviderType + { + Embedded = 0, + Local = 1, + Remote = 2 + } + + public class MetadataResult + where T : IHasMetadata + { + public bool HasMetadata { get; set; } + 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 ProviderIds { get; set; } + + public ItemId() + { + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/MediaBrowser.Controller/Providers/IMetadataService.cs b/MediaBrowser.Controller/Providers/IMetadataService.cs new file mode 100644 index 0000000000..c6cc2b716e --- /dev/null +++ b/MediaBrowser.Controller/Providers/IMetadataService.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Providers +{ + public interface IMetadataService + { + /// + /// Adds the parts. + /// + /// The providers. + /// The image providers. + void AddParts(IEnumerable providers, IEnumerable imageProviders); + + /// + /// Determines whether this instance can refresh the specified item. + /// + /// The item. + /// true if this instance can refresh the specified item; otherwise, false. + bool CanRefresh(IHasMetadata item); + + /// + /// Refreshes the metadata. + /// + /// The item. + /// The options. + /// The cancellation token. + /// Task. + Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken); + + /// + /// Gets the order. + /// + /// The order. + int Order { get; } + } +} diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 728030ecc9..dc57552c44 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -14,15 +14,23 @@ namespace MediaBrowser.Controller.Providers /// public interface IProviderManager { + /// + /// Refreshes the metadata. + /// + /// The item. + /// The options. + /// The cancellation token. + /// Task. + Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken); + /// /// Executes the metadata providers. /// /// The item. /// The cancellation token. /// if set to true [force]. - /// if set to true [allow slow providers]. /// Task{System.Boolean}. - Task ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true); + Task ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false); /// /// Saves the image. @@ -54,7 +62,9 @@ namespace MediaBrowser.Controller.Providers /// /// The providers. /// The image providers. - void AddParts(IEnumerable providers, IEnumerable imageProviders); + /// The metadata services. + /// The metadata providers. + void AddParts(IEnumerable providers, IEnumerable imageProviders, IEnumerable metadataServices, IEnumerable metadataProviders); /// /// Gets the available remote images. @@ -70,7 +80,7 @@ namespace MediaBrowser.Controller.Providers /// Gets the image providers. /// /// The item. - /// IEnumerable{IImageProvider}. - IEnumerable GetImageProviders(BaseItem item); + /// IEnumerable{ImageProviderInfo}. + IEnumerable GetImageProviderInfo(BaseItem item); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs new file mode 100644 index 0000000000..23fda2bfa0 --- /dev/null +++ b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs @@ -0,0 +1,48 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Providers +{ + /// + /// Interface IImageProvider + /// + public interface IRemoteImageProvider : IImageProvider + { + /// + /// Gets the supported images. + /// + /// The item. + /// IEnumerable{ImageType}. + IEnumerable GetSupportedImages(IHasImages item); + + /// + /// Gets the images. + /// + /// The item. + /// Type of the image. + /// The cancellation token. + /// Task{IEnumerable{RemoteImageInfo}}. + Task> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken); + + /// + /// Gets the images. + /// + /// The item. + /// The cancellation token. + /// Task{IEnumerable{RemoteImageInfo}}. + Task> GetAllImages(IHasImages item, CancellationToken cancellationToken); + + /// + /// Gets the image response. + /// + /// The URL. + /// The cancellation token. + /// Task{HttpResponseInfo}. + Task GetImageResponse(string url, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs new file mode 100644 index 0000000000..d6e8a3afea --- /dev/null +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -0,0 +1,49 @@ +using System; + +namespace MediaBrowser.Controller.Providers +{ + public class MetadataRefreshOptions : ImageRefreshOptions + { + /// + /// When paired with MetadataRefreshMode=FullRefresh, all existing data will be overwritten with new data from the providers. + /// + public bool ReplaceAllMetadata { get; set; } + + public MetadataRefreshMode MetadataRefreshMode { get; set; } + + /// + /// TODO: deprecate. Keeping this for now, for api compatibility + /// + [Obsolete] + public bool ForceSave { get; set; } + + /// + /// TODO: deprecate. Keeping this for now, for api compatibility + /// + [Obsolete] + public bool ResetResolveArgs { get; set; } + } + + public class ImageRefreshOptions + { + public MetadataRefreshMode ImageRefreshMode { get; set; } + } + + public enum MetadataRefreshMode + { + /// + /// Providers will be executed based on default rules + /// + EnsureMetadata, + + /// + /// No providers will be executed + /// + None, + + /// + /// All providers will be executed to search for new metadata + /// + FullRefresh + } +} diff --git a/MediaBrowser.Controller/Providers/ProviderResult.cs b/MediaBrowser.Controller/Providers/ProviderResult.cs new file mode 100644 index 0000000000..2486faeedc --- /dev/null +++ b/MediaBrowser.Controller/Providers/ProviderResult.cs @@ -0,0 +1,60 @@ +using System; + +namespace MediaBrowser.Controller.Providers +{ + public class ProviderResult + { + /// + /// Gets or sets the item identifier. + /// + /// The item identifier. + public Guid ItemId { get; set; } + + /// + /// Gets or sets a value indicating whether this instance has refreshed metadata. + /// + /// true if this instance has refreshed metadata; otherwise, false. + public bool HasRefreshedMetadata { get; set; } + + /// + /// Gets or sets a value indicating whether this instance has refreshed images. + /// + /// true if this instance has refreshed images; otherwise, false. + public bool HasRefreshedImages { get; set; } + + /// + /// Gets or sets the date last refreshed. + /// + /// The date last refreshed. + public DateTime DateLastRefreshed { get; set; } + + /// + /// Gets or sets the last result. + /// + /// The last result. + public ProviderRefreshStatus Status { get; set; } + + /// + /// Gets or sets the last result error message. + /// + /// The last result error message. + 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; + } + } +} -- cgit v1.2.3 From 7c5b22246357ef6bcd6d74e61a8fe6a8a9d4df3e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 28 Jan 2014 16:25:10 -0500 Subject: Created ILibraryMonitor to replace IDirectoryWatchers --- .../Library/LibraryStructureService.cs | 37 +- MediaBrowser.Controller/IO/IDirectoryWatchers.cs | 29 - MediaBrowser.Controller/Library/ILibraryMonitor.cs | 36 ++ .../MediaBrowser.Controller.csproj | 2 +- .../Configuration/ServerConfiguration.cs | 4 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 22 +- MediaBrowser.Providers/Manager/MetadataService.cs | 18 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 16 +- .../FileOrganization/EpisodeFileOrganizer.cs | 16 +- .../FileOrganization/FileOrganizationService.cs | 16 +- .../FileOrganization/OrganizerScheduledTask.cs | 8 +- .../FileOrganization/TvFolderOrganizer.cs | 8 +- .../IO/DirectoryWatchers.cs | 636 --------------------- .../IO/LibraryMonitor.cs | 569 ++++++++++++++++++ .../Library/LibraryManager.cs | 16 +- .../MediaBrowser.Server.Implementations.csproj | 2 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 12 +- 17 files changed, 680 insertions(+), 767 deletions(-) delete mode 100644 MediaBrowser.Controller/IO/IDirectoryWatchers.cs create mode 100644 MediaBrowser.Controller/Library/ILibraryMonitor.cs delete mode 100644 MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs create mode 100644 MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 7759073798..56b0e01c3f 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -187,7 +187,7 @@ namespace MediaBrowser.Api.Library /// private readonly ILibraryManager _libraryManager; - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; @@ -199,7 +199,7 @@ namespace MediaBrowser.Api.Library /// The user manager. /// The library manager. /// appPaths - public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger) + public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger) { if (appPaths == null) { @@ -209,7 +209,7 @@ namespace MediaBrowser.Api.Library _userManager = userManager; _appPaths = appPaths; _libraryManager = libraryManager; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _logger = logger; } @@ -270,8 +270,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentException("There is already a media collection with the name " + name + "."); } - _directoryWatchers.Stop(); - _directoryWatchers.TemporarilyIgnore(virtualFolderPath); + _libraryMonitor.Stop(); try { @@ -294,10 +293,8 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } - - _directoryWatchers.RemoveTempIgnore(virtualFolderPath); } if (request.RefreshLibrary) @@ -348,9 +345,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentException("There is already a media collection with the name " + newPath + "."); } - _directoryWatchers.Stop(); - _directoryWatchers.TemporarilyIgnore(currentPath); - _directoryWatchers.TemporarilyIgnore(newPath); + _libraryMonitor.Stop(); try { @@ -376,11 +371,8 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } - - _directoryWatchers.RemoveTempIgnore(currentPath); - _directoryWatchers.RemoveTempIgnore(newPath); } if (request.RefreshLibrary) @@ -420,8 +412,7 @@ namespace MediaBrowser.Api.Library throw new DirectoryNotFoundException("The media folder does not exist"); } - _directoryWatchers.Stop(); - _directoryWatchers.TemporarilyIgnore(path); + _libraryMonitor.Stop(); try { @@ -437,10 +428,8 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } - - _directoryWatchers.RemoveTempIgnore(path); } if (request.RefreshLibrary) @@ -460,7 +449,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentNullException("request"); } - _directoryWatchers.Stop(); + _libraryMonitor.Stop(); try { @@ -485,7 +474,7 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } } @@ -506,7 +495,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentNullException("request"); } - _directoryWatchers.Stop(); + _libraryMonitor.Stop(); try { @@ -531,7 +520,7 @@ namespace MediaBrowser.Api.Library // No need to start if scanning the library because it will handle it if (!request.RefreshLibrary) { - _directoryWatchers.Start(); + _libraryMonitor.Start(); } } diff --git a/MediaBrowser.Controller/IO/IDirectoryWatchers.cs b/MediaBrowser.Controller/IO/IDirectoryWatchers.cs deleted file mode 100644 index 9a43ee8acf..0000000000 --- a/MediaBrowser.Controller/IO/IDirectoryWatchers.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace MediaBrowser.Controller.IO -{ - public interface IDirectoryWatchers : IDisposable - { - /// - /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. - /// - /// The path. - void TemporarilyIgnore(string path); - - /// - /// Removes the temp ignore. - /// - /// The path. - void RemoveTempIgnore(string path); - - /// - /// Starts this instance. - /// - void Start(); - - /// - /// Stops this instance. - /// - void Stop(); - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Library/ILibraryMonitor.cs b/MediaBrowser.Controller/Library/ILibraryMonitor.cs new file mode 100644 index 0000000000..918382f049 --- /dev/null +++ b/MediaBrowser.Controller/Library/ILibraryMonitor.cs @@ -0,0 +1,36 @@ +using System; + +namespace MediaBrowser.Controller.Library +{ + public interface ILibraryMonitor : IDisposable + { + /// + /// Starts this instance. + /// + void Start(); + + /// + /// Stops this instance. + /// + void Stop(); + + /// + /// Reports the file system change beginning. + /// + /// The path. + void ReportFileSystemChangeBeginning(string path); + + /// + /// Reports the file system change complete. + /// + /// The path. + /// if set to true [refresh path]. + void ReportFileSystemChangeComplete(string path, bool refreshPath); + + /// + /// Reports the file system changed. + /// + /// The path. + void ReportFileSystemChanged(string path); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index ef87c30c78..45297ef3d9 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -182,7 +182,7 @@ - + diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index f017fdf16f..923c5ab748 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -165,7 +165,7 @@ namespace MediaBrowser.Model.Configuration /// different directories and files. /// /// The file watcher delay. - public int FileWatcherDelay { get; set; } + public int RealtimeWatcherDelay { get; set; } /// /// Gets or sets a value indicating whether [enable dashboard response caching]. @@ -250,7 +250,7 @@ namespace MediaBrowser.Model.Configuration MaxResumePct = 90; MinResumeDurationSeconds = Convert.ToInt32(TimeSpan.FromMinutes(5).TotalSeconds); - FileWatcherDelay = 8; + RealtimeWatcherDelay = 20; RecentItemDays = 10; diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index a75ce88aeb..56a1a9d4fe 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager /// /// The _directory watchers /// - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; @@ -44,11 +44,11 @@ namespace MediaBrowser.Providers.Manager /// Initializes a new instance of the class. /// /// The config. - /// The directory watchers. - public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger) + /// The directory watchers. + public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger) { _config = config; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _logger = logger; _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath); @@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.Manager // Delete the current path if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase)) { - _directoryWatchers.TemporarilyIgnore(currentPath); + _libraryMonitor.ReportFileSystemChangeBeginning(currentPath); try { @@ -179,7 +179,7 @@ namespace MediaBrowser.Providers.Manager } finally { - _directoryWatchers.RemoveTempIgnore(currentPath); + _libraryMonitor.ReportFileSystemChangeComplete(currentPath, false); } } } @@ -197,8 +197,8 @@ namespace MediaBrowser.Providers.Manager var parentFolder = Path.GetDirectoryName(path); - _directoryWatchers.TemporarilyIgnore(path); - _directoryWatchers.TemporarilyIgnore(parentFolder); + _libraryMonitor.ReportFileSystemChangeBeginning(path); + _libraryMonitor.ReportFileSystemChangeBeginning(parentFolder); try { @@ -223,8 +223,8 @@ namespace MediaBrowser.Providers.Manager } finally { - _directoryWatchers.RemoveTempIgnore(path); - _directoryWatchers.RemoveTempIgnore(parentFolder); + _libraryMonitor.ReportFileSystemChangeComplete(path, false); + _libraryMonitor.ReportFileSystemChangeComplete(parentFolder, false); } } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index dbe70b93d7..5dde3098ac 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -38,7 +38,6 @@ namespace MediaBrowser.Providers.Manager public void AddParts(IEnumerable providers, IEnumerable imageProviders) { _providers = providers.OfType>() - .OrderBy(GetSortOrder) .ToArray(); _imageProviders = imageProviders.OrderBy(i => i.Order).ToArray(); @@ -179,21 +178,6 @@ namespace MediaBrowser.Providers.Manager return providers; } - /// - /// Gets the sort order. - /// - /// The provider. - /// System.Int32. - protected virtual int GetSortOrder(IMetadataProvider provider) - { - if (provider is IRemoteMetadataProvider) - { - return 1; - } - - return 0; - } - /// /// Determines whether this instance can refresh the specified provider. /// @@ -217,7 +201,7 @@ namespace MediaBrowser.Providers.Manager protected abstract Task SaveItem(TItemType item, ItemUpdateType reason, CancellationToken cancellationToken); - protected virtual ItemId GetId(TItemType item) + protected virtual ItemId GetId(IHasMetadata item) { return new ItemId { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index ebad2e6e05..faad156698 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager /// /// The _directory watchers /// - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; /// /// Gets or sets the configuration manager. @@ -57,23 +57,23 @@ namespace MediaBrowser.Providers.Manager private readonly IItemRepository _itemRepo; - private IMetadataService[] _metadataServices = {}; + private IMetadataService[] _metadataServices = { }; /// /// Initializes a new instance of the class. /// /// The HTTP client. /// The configuration manager. - /// The directory watchers. + /// The directory watchers. /// The log manager. /// The file system. /// The item repo. - public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo) + public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo) { _logger = logManager.GetLogger("ProviderManager"); _httpClient = httpClient; ConfigurationManager = configurationManager; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _fileSystem = fileSystem; _itemRepo = itemRepo; } @@ -315,7 +315,7 @@ namespace MediaBrowser.Providers.Manager } //Tell the watchers to ignore - _directoryWatchers.TemporarilyIgnore(path); + _libraryMonitor.ReportFileSystemChangeBeginning(path); if (dataToSave.CanSeek) { @@ -338,7 +338,7 @@ namespace MediaBrowser.Providers.Manager finally { //Remove the ignore - _directoryWatchers.RemoveTempIgnore(path); + _libraryMonitor.ReportFileSystemChangeComplete(path, false); } } @@ -380,7 +380,7 @@ namespace MediaBrowser.Providers.Manager /// Task. public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken) { - return new ImageSaver(ConfigurationManager, _directoryWatchers, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken); + return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken); } /// diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index 0a2dd5ae04..a2e094e9aa 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class EpisodeFileOrganizer { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; @@ -31,14 +31,14 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers) + public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor) { _organizationService = organizationService; _config = config; _fileSystem = fileSystem; _logger = logger; _libraryManager = libraryManager; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; } public async Task OrganizeEpisodeFile(string path, TvFileOrganizationOptions options, bool overwriteExisting) @@ -174,6 +174,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { _logger.Debug("Removing duplicate episode {0}", path); + _libraryMonitor.ReportFileSystemChangeBeginning(path); + try { File.Delete(path); @@ -182,6 +184,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { _logger.ErrorException("Error removing duplicate episode", ex, path); } + finally + { + _libraryMonitor.ReportFileSystemChangeComplete(path, true); + } } } } @@ -232,7 +238,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result) { - _directoryWatchers.TemporarilyIgnore(result.TargetPath); + _libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath); Directory.CreateDirectory(Path.GetDirectoryName(result.TargetPath)); @@ -264,7 +270,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } finally { - _directoryWatchers.RemoveTempIgnore(result.TargetPath); + _libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true); } if (copy) diff --git a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs index bbd0f74e5b..518a7bb48f 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs @@ -21,17 +21,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private readonly ITaskManager _taskManager; private readonly IFileOrganizationRepository _repo; private readonly ILogger _logger; - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; - public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) + public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) { _taskManager = taskManager; _repo = repo; _logger = logger; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _libraryManager = libraryManager; _config = config; _fileSystem = fileSystem; @@ -91,13 +91,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization } var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); await organizer.OrganizeEpisodeFile(result.OriginalPath, _config.Configuration.TvFileOrganizationOptions, true) .ConfigureAwait(false); - - await _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None) - .ConfigureAwait(false); } public Task ClearLog() @@ -108,12 +105,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request) { var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); await organizer.OrganizeWithCorrection(request, _config.Configuration.TvFileOrganizationOptions).ConfigureAwait(false); - - await _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None) - .ConfigureAwait(false); } } } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs index 340038e4b0..3c5e1ed0ed 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs @@ -14,16 +14,16 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _config; private readonly IFileOrganizationService _organizationService; - public OrganizerScheduledTask(IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService) + public OrganizerScheduledTask(ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService) { - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _libraryManager = libraryManager; _logger = logger; _fileSystem = fileSystem; @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public Task Execute(CancellationToken cancellationToken, IProgress progress) { - return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _directoryWatchers, _organizationService, _config) + return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config) .Organize(_config.Configuration.TvFileOrganizationOptions, cancellationToken, progress); } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs index 6a413f2f04..24f21e3398 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs @@ -18,19 +18,19 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { public class TvFolderOrganizer { - private readonly IDirectoryWatchers _directoryWatchers; + private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IFileOrganizationService _organizationService; private readonly IServerConfigurationManager _config; - public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IDirectoryWatchers directoryWatchers, IFileOrganizationService organizationService, IServerConfigurationManager config) + public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config) { _libraryManager = libraryManager; _logger = logger; _fileSystem = fileSystem; - _directoryWatchers = directoryWatchers; + _libraryMonitor = libraryMonitor; _organizationService = organizationService; _config = config; } @@ -57,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization foreach (var file in eligibleFiles) { var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, - _directoryWatchers); + _libraryMonitor); var result = await organizer.OrganizeEpisodeFile(file.FullName, options, false).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs deleted file mode 100644 index 1efc3bc70f..0000000000 --- a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs +++ /dev/null @@ -1,636 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Server.Implementations.ScheduledTasks; -using Microsoft.Win32; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.IO -{ - /// - /// Class DirectoryWatchers - /// - public class DirectoryWatchers : IDirectoryWatchers - { - /// - /// The file system watchers - /// - private readonly ConcurrentDictionary _fileSystemWatchers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - /// - /// The update timer - /// - private Timer _updateTimer; - /// - /// The affected paths - /// - private readonly ConcurrentDictionary _affectedPaths = new ConcurrentDictionary(); - - /// - /// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications. - /// - private readonly ConcurrentDictionary _tempIgnoredPaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Any file name ending in any of these will be ignored by the watchers - /// - private readonly IReadOnlyList _alwaysIgnoreFiles = new List { "thumbs.db", "small.jpg", "albumart.jpg" }; - - /// - /// The timer lock - /// - private readonly object _timerLock = new object(); - - /// - /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. - /// - /// The path. - public void TemporarilyIgnore(string path) - { - _tempIgnoredPaths[path] = path; - } - - /// - /// Removes the temp ignore. - /// - /// The path. - public 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); - } - - /// - /// Gets or sets the logger. - /// - /// The logger. - private ILogger Logger { get; set; } - - /// - /// Gets or sets the task manager. - /// - /// The task manager. - private ITaskManager TaskManager { get; set; } - - private ILibraryManager LibraryManager { get; set; } - private IServerConfigurationManager ConfigurationManager { get; set; } - - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - public DirectoryWatchers(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - { - if (taskManager == null) - { - throw new ArgumentNullException("taskManager"); - } - - LibraryManager = libraryManager; - TaskManager = taskManager; - Logger = logManager.GetLogger("DirectoryWatchers"); - ConfigurationManager = configurationManager; - _fileSystem = fileSystem; - - SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; - } - - /// - /// Handles the PowerModeChanged event of the SystemEvents control. - /// - /// The source of the event. - /// The instance containing the event data. - void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) - { - Stop(); - Start(); - } - - /// - /// Starts this instance. - /// - public void Start() - { - LibraryManager.ItemAdded += LibraryManager_ItemAdded; - LibraryManager.ItemRemoved += LibraryManager_ItemRemoved; - - var pathsToWatch = new List { LibraryManager.RootFolder.Path }; - - var paths = LibraryManager - .RootFolder - .Children - .OfType() - .Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual) - .SelectMany(f => - { - try - { - // Accessing ResolveArgs could involve file system access - return f.ResolveArgs.PhysicalLocations; - } - catch (IOException) - { - return new string[] { }; - } - - }) - .Distinct(StringComparer.OrdinalIgnoreCase) - .OrderBy(i => i) - .ToList(); - - foreach (var path in paths) - { - if (!ContainsParentFolder(pathsToWatch, path)) - { - pathsToWatch.Add(path); - } - } - - foreach (var path in pathsToWatch) - { - StartWatchingPath(path); - } - } - - /// - /// Handles the ItemRemoved event of the LibraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) - { - if (e.Item.Parent is AggregateFolder) - { - StopWatchingPath(e.Item.Path); - } - } - - /// - /// Handles the ItemAdded event of the LibraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e) - { - if (e.Item.Parent is AggregateFolder) - { - StartWatchingPath(e.Item.Path); - } - } - - /// - /// Examine a list of strings assumed to be file paths to see if it contains a parent of - /// the provided path. - /// - /// The LST. - /// The path. - /// true if [contains parent folder] [the specified LST]; otherwise, false. - /// path - private static bool ContainsParentFolder(IEnumerable lst, string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - path = path.TrimEnd(Path.DirectorySeparatorChar); - - return lst.Any(str => - { - //this should be a little quicker than examining each actual parent folder... - var compare = str.TrimEnd(Path.DirectorySeparatorChar); - - return (path.Equals(compare, StringComparison.OrdinalIgnoreCase) || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == Path.DirectorySeparatorChar)); - }); - } - - /// - /// Starts the watching path. - /// - /// The path. - private void StartWatchingPath(string path) - { - // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel - Task.Run(() => - { - var newWatcher = new FileSystemWatcher(path, "*") { IncludeSubdirectories = true, InternalBufferSize = 32767 }; - - newWatcher.Created += watcher_Changed; - newWatcher.Deleted += watcher_Changed; - newWatcher.Renamed += watcher_Changed; - newWatcher.Changed += watcher_Changed; - - newWatcher.Error += watcher_Error; - - try - { - if (_fileSystemWatchers.TryAdd(path, newWatcher)) - { - newWatcher.EnableRaisingEvents = true; - Logger.Info("Watching directory " + path); - } - else - { - Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary." + path); - newWatcher.Dispose(); - } - - } - catch (IOException ex) - { - Logger.ErrorException("Error watching path: {0}", ex, path); - } - catch (PlatformNotSupportedException ex) - { - Logger.ErrorException("Error watching path: {0}", ex, path); - } - }); - } - - /// - /// Stops the watching path. - /// - /// The path. - private void StopWatchingPath(string path) - { - FileSystemWatcher watcher; - - if (_fileSystemWatchers.TryGetValue(path, out watcher)) - { - DisposeWatcher(watcher); - } - } - - /// - /// Disposes the watcher. - /// - /// The watcher. - private void DisposeWatcher(FileSystemWatcher watcher) - { - Logger.Info("Stopping directory watching for path {0}", watcher.Path); - - watcher.EnableRaisingEvents = false; - watcher.Dispose(); - - RemoveWatcherFromList(watcher); - } - - /// - /// Removes the watcher from list. - /// - /// The watcher. - private void RemoveWatcherFromList(FileSystemWatcher watcher) - { - FileSystemWatcher removed; - - _fileSystemWatchers.TryRemove(watcher.Path, out removed); - } - - /// - /// Handles the Error event of the watcher control. - /// - /// The source of the event. - /// The instance containing the event data. - void watcher_Error(object sender, ErrorEventArgs e) - { - var ex = e.GetException(); - var dw = (FileSystemWatcher)sender; - - Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex); - - DisposeWatcher(dw); - } - - /// - /// Handles the Changed event of the watcher control. - /// - /// The source of the event. - /// The instance containing the event data. - void watcher_Changed(object sender, FileSystemEventArgs e) - { - try - { - OnWatcherChanged(e); - } - catch (IOException ex) - { - Logger.ErrorException("IOException in watcher changed. Path: {0}", ex, e.FullPath); - } - } - - private void OnWatcherChanged(FileSystemEventArgs e) - { - var name = e.Name; - - // Ignore certain files - if (_alwaysIgnoreFiles.Contains(name, StringComparer.OrdinalIgnoreCase)) - { - return; - } - - var nameFromFullPath = Path.GetFileName(e.FullPath); - // Ignore certain files - if (!string.IsNullOrEmpty(nameFromFullPath) && _alwaysIgnoreFiles.Contains(nameFromFullPath, StringComparer.OrdinalIgnoreCase)) - { - return; - } - - // Ignore when someone manually creates a new folder - if (e.ChangeType == WatcherChangeTypes.Created && name == "New folder") - { - return; - } - - var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); - - // If the parent of an ignored path has a change event, ignore that too - if (tempIgnorePaths.Any(i => - { - if (string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - - // Go up a level - var parent = Path.GetDirectoryName(i); - if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - - // Go up another level - if (!string.IsNullOrEmpty(parent)) - { - parent = Path.GetDirectoryName(i); - if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - } - - if (i.StartsWith(e.FullPath, StringComparison.OrdinalIgnoreCase) || - e.FullPath.StartsWith(i, StringComparison.OrdinalIgnoreCase)) - { - Logger.Debug("Watcher ignoring change to {0}", e.FullPath); - return true; - } - - return false; - - })) - { - return; - } - - Logger.Info("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); - - //Since we're watching created, deleted and renamed we always want the parent of the item to be the affected path - var affectedPath = e.FullPath; - - _affectedPaths.AddOrUpdate(affectedPath, affectedPath, (key, oldValue) => affectedPath); - - lock (_timerLock) - { - if (_updateTimer == null) - { - _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); - } - else - { - _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); - } - } - } - - /// - /// Timers the stopped. - /// - /// The state info. - private async void TimerStopped(object stateInfo) - { - lock (_timerLock) - { - // Extend the timer as long as any of the paths are still being written to. - if (_affectedPaths.Any(p => IsFileLocked(p.Key))) - { - Logger.Info("Timer extended."); - _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1)); - return; - } - - Logger.Info("Timer stopped."); - - if (_updateTimer != null) - { - _updateTimer.Dispose(); - _updateTimer = null; - } - } - - var paths = _affectedPaths.Keys.ToList(); - _affectedPaths.Clear(); - - await ProcessPathChanges(paths).ConfigureAwait(false); - } - - /// - /// Try and determine if a file is locked - /// This is not perfect, and is subject to race conditions, so I'd rather not make this a re-usable library method. - /// - /// The path. - /// true if [is file locked] [the specified path]; otherwise, false. - private bool IsFileLocked(string path) - { - try - { - var data = _fileSystem.GetFileSystemInfo(path); - - if (!data.Exists - || data.Attributes.HasFlag(FileAttributes.Directory) - || data.Attributes.HasFlag(FileAttributes.ReadOnly)) - { - return false; - } - } - catch (IOException) - { - return false; - } - - try - { - using (_fileSystem.GetFileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) - { - //file is not locked - return false; - } - } - catch (DirectoryNotFoundException) - { - return false; - } - catch (FileNotFoundException) - { - return false; - } - catch (IOException) - { - //the file is unavailable because it is: - //still being written to - //or being processed by another thread - //or does not exist (has already been processed) - Logger.Debug("{0} is locked.", path); - return true; - } - catch - { - return false; - } - } - - /// - /// Processes the path changes. - /// - /// The paths. - /// Task. - private async Task ProcessPathChanges(List paths) - { - var itemsToRefresh = paths.Select(Path.GetDirectoryName) - .Select(GetAffectedBaseItem) - .Where(item => item != null) - .Distinct() - .ToList(); - - foreach (var p in paths) Logger.Info(p + " reports change."); - - // If the root folder changed, run the library task so the user can see it - if (itemsToRefresh.Any(i => i is AggregateFolder)) - { - TaskManager.CancelIfRunningAndQueue(); - return; - } - - foreach (var item in itemsToRefresh) - { - Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); - - try - { - await item.ChangedExternally().ConfigureAwait(false); - } - catch (IOException ex) - { - // For now swallow and log. - // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) - // Should we remove it from it's parent? - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } - } - } - - /// - /// Gets the affected base item. - /// - /// The path. - /// BaseItem. - private BaseItem GetAffectedBaseItem(string path) - { - BaseItem item = null; - - while (item == null && !string.IsNullOrEmpty(path)) - { - item = LibraryManager.RootFolder.FindByPath(path); - - path = Path.GetDirectoryName(path); - } - - if (item != null) - { - // If the item has been deleted find the first valid parent that still exists - while (!Directory.Exists(item.Path) && !File.Exists(item.Path)) - { - item = item.Parent; - - if (item == null) - { - break; - } - } - } - - return item; - } - - /// - /// Stops this instance. - /// - public void Stop() - { - LibraryManager.ItemAdded -= LibraryManager_ItemAdded; - LibraryManager.ItemRemoved -= LibraryManager_ItemRemoved; - - foreach (var watcher in _fileSystemWatchers.Values.ToList()) - { - watcher.Changed -= watcher_Changed; - watcher.EnableRaisingEvents = false; - watcher.Dispose(); - } - - lock (_timerLock) - { - if (_updateTimer != null) - { - _updateTimer.Dispose(); - _updateTimer = null; - } - } - - _fileSystemWatchers.Clear(); - _affectedPaths.Clear(); - } - - /// - /// 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) - { - if (dispose) - { - Stop(); - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs new file mode 100644 index 0000000000..e09e667651 --- /dev/null +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -0,0 +1,569 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.ScheduledTasks; +using Microsoft.Win32; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.IO +{ + public class LibraryMonitor : ILibraryMonitor + { + /// + /// The file system watchers + /// + private readonly ConcurrentDictionary _fileSystemWatchers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + /// + /// The update timer + /// + private Timer _updateTimer; + /// + /// The affected paths + /// + private readonly ConcurrentDictionary _affectedPaths = new ConcurrentDictionary(); + + /// + /// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications. + /// + private readonly ConcurrentDictionary _tempIgnoredPaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Any file name ending in any of these will be ignored by the watchers + /// + private readonly IReadOnlyList _alwaysIgnoreFiles = new List { "thumbs.db", "small.jpg", "albumart.jpg" }; + + /// + /// The timer lock + /// + private readonly object _timerLock = new object(); + + /// + /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. + /// + /// The path. + private void TemporarilyIgnore(string path) + { + _tempIgnoredPaths[path] = path; + } + + /// + /// Removes the temp ignore. + /// + /// The path. + 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) + { + TemporarilyIgnore(path); + } + + public void ReportFileSystemChangeComplete(string path, bool refreshPath) + { + RemoveTempIgnore(path); + + if (refreshPath) + { + ReportFileSystemChanged(path); + } + } + + /// + /// Gets or sets the logger. + /// + /// The logger. + private ILogger Logger { get; set; } + + /// + /// Gets or sets the task manager. + /// + /// The task manager. + private ITaskManager TaskManager { get; set; } + + private ILibraryManager LibraryManager { get; set; } + private IServerConfigurationManager ConfigurationManager { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager) + { + if (taskManager == null) + { + throw new ArgumentNullException("taskManager"); + } + + LibraryManager = libraryManager; + TaskManager = taskManager; + Logger = logManager.GetLogger(GetType().Name); + ConfigurationManager = configurationManager; + + SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; + } + + /// + /// Handles the PowerModeChanged event of the SystemEvents control. + /// + /// The source of the event. + /// The instance containing the event data. + void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) + { + Stop(); + Start(); + } + + /// + /// Starts this instance. + /// + public void Start() + { + LibraryManager.ItemAdded += LibraryManager_ItemAdded; + LibraryManager.ItemRemoved += LibraryManager_ItemRemoved; + + var pathsToWatch = new List { LibraryManager.RootFolder.Path }; + + var paths = LibraryManager + .RootFolder + .Children + .OfType() + .Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual) + .SelectMany(f => + { + try + { + // Accessing ResolveArgs could involve file system access + return f.ResolveArgs.PhysicalLocations; + } + catch (IOException) + { + return new string[] { }; + } + + }) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(i => i) + .ToList(); + + foreach (var path in paths) + { + if (!ContainsParentFolder(pathsToWatch, path)) + { + pathsToWatch.Add(path); + } + } + + foreach (var path in pathsToWatch) + { + StartWatchingPath(path); + } + } + + /// + /// Handles the ItemRemoved event of the LibraryManager control. + /// + /// The source of the event. + /// The instance containing the event data. + void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) + { + if (e.Item.Parent is AggregateFolder) + { + StopWatchingPath(e.Item.Path); + } + } + + /// + /// Handles the ItemAdded event of the LibraryManager control. + /// + /// The source of the event. + /// The instance containing the event data. + void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + if (e.Item.Parent is AggregateFolder) + { + StartWatchingPath(e.Item.Path); + } + } + + /// + /// Examine a list of strings assumed to be file paths to see if it contains a parent of + /// the provided path. + /// + /// The LST. + /// The path. + /// true if [contains parent folder] [the specified LST]; otherwise, false. + /// path + private static bool ContainsParentFolder(IEnumerable lst, string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + path = path.TrimEnd(Path.DirectorySeparatorChar); + + return lst.Any(str => + { + //this should be a little quicker than examining each actual parent folder... + var compare = str.TrimEnd(Path.DirectorySeparatorChar); + + return (path.Equals(compare, StringComparison.OrdinalIgnoreCase) || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == Path.DirectorySeparatorChar)); + }); + } + + /// + /// Starts the watching path. + /// + /// The path. + private void StartWatchingPath(string path) + { + // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel + Task.Run(() => + { + var newWatcher = new FileSystemWatcher(path, "*") { IncludeSubdirectories = true, InternalBufferSize = 32767 }; + + newWatcher.Created += watcher_Changed; + newWatcher.Deleted += watcher_Changed; + newWatcher.Renamed += watcher_Changed; + newWatcher.Changed += watcher_Changed; + + newWatcher.Error += watcher_Error; + + try + { + if (_fileSystemWatchers.TryAdd(path, newWatcher)) + { + newWatcher.EnableRaisingEvents = true; + Logger.Info("Watching directory " + path); + } + else + { + Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary." + path); + newWatcher.Dispose(); + } + + } + catch (IOException ex) + { + Logger.ErrorException("Error watching path: {0}", ex, path); + } + catch (PlatformNotSupportedException ex) + { + Logger.ErrorException("Error watching path: {0}", ex, path); + } + }); + } + + /// + /// Stops the watching path. + /// + /// The path. + private void StopWatchingPath(string path) + { + FileSystemWatcher watcher; + + if (_fileSystemWatchers.TryGetValue(path, out watcher)) + { + DisposeWatcher(watcher); + } + } + + /// + /// Disposes the watcher. + /// + /// The watcher. + private void DisposeWatcher(FileSystemWatcher watcher) + { + Logger.Info("Stopping directory watching for path {0}", watcher.Path); + + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + + RemoveWatcherFromList(watcher); + } + + /// + /// Removes the watcher from list. + /// + /// The watcher. + private void RemoveWatcherFromList(FileSystemWatcher watcher) + { + FileSystemWatcher removed; + + _fileSystemWatchers.TryRemove(watcher.Path, out removed); + } + + /// + /// Handles the Error event of the watcher control. + /// + /// The source of the event. + /// The instance containing the event data. + void watcher_Error(object sender, ErrorEventArgs e) + { + var ex = e.GetException(); + var dw = (FileSystemWatcher)sender; + + Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex); + + DisposeWatcher(dw); + } + + /// + /// Handles the Changed event of the watcher control. + /// + /// The source of the event. + /// The instance containing the event data. + void watcher_Changed(object sender, FileSystemEventArgs e) + { + try + { + OnWatcherChanged(e); + } + catch (Exception ex) + { + Logger.ErrorException("Exception in watcher changed. Path: {0}", ex, e.FullPath); + } + } + + private void OnWatcherChanged(FileSystemEventArgs e) + { + Logger.Debug("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); + + ReportFileSystemChanged(e.FullPath); + } + + public void ReportFileSystemChanged(string path) + { + var filename = Path.GetFileName(path); + + // Ignore certain files + if (!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)) + { + return; + } + + var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); + + // If the parent of an ignored path has a change event, ignore that too + if (tempIgnorePaths.Any(i => + { + if (string.Equals(i, path, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + + // Go up a level + var parent = Path.GetDirectoryName(i); + if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + + // Go up another level + if (!string.IsNullOrEmpty(parent)) + { + parent = Path.GetDirectoryName(i); + if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + } + + if (i.StartsWith(path, StringComparison.OrdinalIgnoreCase) || + path.StartsWith(i, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug("Ignoring change to {0}", path); + return true; + } + + return false; + + })) + { + return; + } + + // Avoid implicitly captured closure + var affectedPath = path; + _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath); + + lock (_timerLock) + { + if (_updateTimer == null) + { + _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1)); + } + else + { + _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1)); + } + } + } + + /// + /// Timers the stopped. + /// + /// The state info. + private async void TimerStopped(object stateInfo) + { + Logger.Debug("Timer stopped."); + + DisposeTimer(); + + var paths = _affectedPaths.Keys.ToList(); + _affectedPaths.Clear(); + + await ProcessPathChanges(paths).ConfigureAwait(false); + } + + private void DisposeTimer() + { + lock (_timerLock) + { + if (_updateTimer != null) + { + _updateTimer.Dispose(); + _updateTimer = null; + } + } + } + + /// + /// Processes the path changes. + /// + /// The paths. + /// Task. + private async Task ProcessPathChanges(List paths) + { + var itemsToRefresh = paths.Select(Path.GetDirectoryName) + .Select(GetAffectedBaseItem) + .Where(item => item != null) + .Distinct() + .ToList(); + + foreach (var p in paths) Logger.Info(p + " reports change."); + + // If the root folder changed, run the library task so the user can see it + if (itemsToRefresh.Any(i => i is AggregateFolder)) + { + TaskManager.CancelIfRunningAndQueue(); + return; + } + + foreach (var item in itemsToRefresh) + { + Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); + + try + { + await item.ChangedExternally().ConfigureAwait(false); + } + catch (IOException ex) + { + // For now swallow and log. + // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) + // Should we remove it from it's parent? + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + catch (Exception ex) + { + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + } + } + + /// + /// Gets the affected base item. + /// + /// The path. + /// BaseItem. + private BaseItem GetAffectedBaseItem(string path) + { + BaseItem item = null; + + while (item == null && !string.IsNullOrEmpty(path)) + { + item = LibraryManager.RootFolder.FindByPath(path); + + path = Path.GetDirectoryName(path); + } + + if (item != null) + { + // If the item has been deleted find the first valid parent that still exists + while (!Directory.Exists(item.Path) && !File.Exists(item.Path)) + { + item = item.Parent; + + if (item == null) + { + break; + } + } + } + + return item; + } + + /// + /// Stops this instance. + /// + public void Stop() + { + LibraryManager.ItemAdded -= LibraryManager_ItemAdded; + LibraryManager.ItemRemoved -= LibraryManager_ItemRemoved; + + foreach (var watcher in _fileSystemWatchers.Values.ToList()) + { + watcher.Changed -= watcher_Changed; + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + + DisposeTimer(); + + _fileSystemWatchers.Clear(); + _affectedPaths.Clear(); + } + + /// + /// 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) + { + if (dispose) + { + Stop(); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 736c70ad59..04344553fc 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library private IEnumerable _savers; - private readonly Func _directoryWatchersFactory; + private readonly Func _libraryMonitorFactory; /// /// The _library items cache @@ -180,14 +180,14 @@ namespace MediaBrowser.Server.Implementations.Library /// The user manager. /// The configuration manager. /// The user data repository. - public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func directoryWatchersFactory, IFileSystem fileSystem) + public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func libraryMonitorFactory, IFileSystem fileSystem) { _logger = logger; _taskManager = taskManager; _userManager = userManager; ConfigurationManager = configurationManager; _userDataRepository = userDataRepository; - _directoryWatchersFactory = directoryWatchersFactory; + _libraryMonitorFactory = libraryMonitorFactory; _fileSystem = fileSystem; ByReferenceItems = new ConcurrentDictionary(); @@ -934,7 +934,7 @@ namespace MediaBrowser.Server.Implementations.Library /// Task. public async Task ValidateMediaLibraryInternal(IProgress progress, CancellationToken cancellationToken) { - _directoryWatchersFactory().Stop(); + _libraryMonitorFactory().Stop(); try { @@ -942,7 +942,7 @@ namespace MediaBrowser.Server.Implementations.Library } finally { - _directoryWatchersFactory().Start(); + _libraryMonitorFactory().Start(); } } @@ -1462,13 +1462,13 @@ namespace MediaBrowser.Server.Implementations.Library var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1)); - var directoryWatchers = _directoryWatchersFactory(); + var directoryWatchers = _libraryMonitorFactory(); await semaphore.WaitAsync().ConfigureAwait(false); try { - directoryWatchers.TemporarilyIgnore(path); + directoryWatchers.ReportFileSystemChangeBeginning(path); saver.Save(item, CancellationToken.None); } catch (Exception ex) @@ -1477,7 +1477,7 @@ namespace MediaBrowser.Server.Implementations.Library } finally { - directoryWatchers.RemoveTempIgnore(path); + directoryWatchers.ReportFileSystemChangeComplete(path, false); semaphore.Release(); } } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index a92ec29d51..fe4283368c 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -137,7 +137,7 @@ - + diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 3553996b06..0c8a6b923b 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -137,7 +137,7 @@ namespace MediaBrowser.ServerApplication /// Gets or sets the directory watchers. /// /// The directory watchers. - private IDirectoryWatchers DirectoryWatchers { get; set; } + private ILibraryMonitor LibraryMonitor { get; set; } /// /// Gets or sets the provider manager. /// @@ -273,13 +273,13 @@ namespace MediaBrowser.ServerApplication UserManager = new UserManager(Logger, ServerConfigurationManager, UserRepository); RegisterSingleInstance(UserManager); - LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => DirectoryWatchers, FileSystemManager); + LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager); RegisterSingleInstance(LibraryManager); - DirectoryWatchers = new DirectoryWatchers(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager); - RegisterSingleInstance(DirectoryWatchers); + LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager); + RegisterSingleInstance(LibraryMonitor); - ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, DirectoryWatchers, LogManager, FileSystemManager, ItemRepository); + ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ItemRepository); RegisterSingleInstance(ProviderManager); RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); @@ -306,7 +306,7 @@ namespace MediaBrowser.ServerApplication var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); RegisterSingleInstance(newsService); - var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, DirectoryWatchers, LibraryManager, ServerConfigurationManager, FileSystemManager); + var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, LibraryMonitor, LibraryManager, ServerConfigurationManager, FileSystemManager); RegisterSingleInstance(fileOrganizationService); progress.Report(15); -- cgit v1.2.3 From 81d5e9f8087227591b2be068e822342b17ef3d7a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 29 Jan 2014 00:17:58 -0500 Subject: persist provider results --- MediaBrowser.Controller/Entities/BaseItem.cs | 7 +- .../MediaBrowser.Controller.csproj | 4 +- .../Persistence/IItemRepository.cs | 17 -- .../Providers/IMetadataProvider.cs | 28 +-- .../Providers/IProviderRepository.cs | 48 +++++ MediaBrowser.Controller/Providers/ItemId.cs | 35 ++++ .../Providers/MetadataStatus.cs | 122 ++++++++++++ .../Providers/ProviderResult.cs | 60 ------ .../GameGenres/GameGenreMetadataService.cs | 4 +- .../Genres/GenreMetadataService.cs | 4 +- .../Manager/ItemImageProvider.cs | 11 +- MediaBrowser.Providers/Manager/MetadataService.cs | 51 ++--- MediaBrowser.Providers/Manager/ProviderManager.cs | 12 +- .../MusicGenres/MusicGenreMetadataService.cs | 4 +- .../People/PersonMetadataService.cs | 4 +- .../Studios/StudioMetadataService.cs | 4 +- .../IO/LibraryMonitor.cs | 25 +-- .../Persistence/SqliteItemRepository.cs | 22 --- .../Persistence/SqliteProviderInfoRepository.cs | 212 ++++++++++++++++++--- MediaBrowser.ServerApplication/ApplicationHost.cs | 9 +- 20 files changed, 477 insertions(+), 206 deletions(-) create mode 100644 MediaBrowser.Controller/Providers/IProviderRepository.cs create mode 100644 MediaBrowser.Controller/Providers/ItemId.cs create mode 100644 MediaBrowser.Controller/Providers/MetadataStatus.cs delete mode 100644 MediaBrowser.Controller/Providers/ProviderResult.cs (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index a50a0f4674..06ebe89055 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -364,11 +364,16 @@ namespace MediaBrowser.Controller.Entities } } + private string _forcedSortName; /// /// Gets or sets the name of the forced sort. /// /// The name of the forced sort. - public string ForcedSortName { get; set; } + public string ForcedSortName + { + get { return _forcedSortName; } + set { _forcedSortName = value; _sortName = null; } + } private string _sortName; /// diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 45297ef3d9..ee8bb27619 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -146,13 +146,15 @@ + + - + diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 3affe48e74..3a5cb4e870 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/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 /// The cancellation token. /// Task. Task SaveMediaStreams(Guid id, IEnumerable streams, CancellationToken cancellationToken); - - /// - /// Gets the provider history. - /// - /// The item identifier. - /// IEnumerable{BaseProviderInfo}. - IEnumerable GetProviderHistory(Guid itemId); - - /// - /// Saves the provider history. - /// - /// The identifier. - /// The history. - /// The cancellation token. - /// Task. - Task SaveProviderHistory(Guid id, IEnumerable history, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Providers/IMetadataProvider.cs b/MediaBrowser.Controller/Providers/IMetadataProvider.cs index 97249e26d9..843ba263bc 100644 --- a/MediaBrowser.Controller/Providers/IMetadataProvider.cs +++ b/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 { /// - /// Determines whether the specified date has changed. + /// Determines whether the specified item has changed. /// /// The item. /// The date. - /// true if the specified date has changed; otherwise, false. + /// true if the specified item has changed; otherwise, false. bool HasChanged(IHasMetadata item, DateTime date); } - public enum MetadataProviderType - { - Embedded = 0, - Local = 1, - Remote = 2 - } - public class MetadataResult 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 ProviderIds { get; set; } - - public ItemId() - { - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - } } diff --git a/MediaBrowser.Controller/Providers/IProviderRepository.cs b/MediaBrowser.Controller/Providers/IProviderRepository.cs new file mode 100644 index 0000000000..1c0ad2cd73 --- /dev/null +++ b/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 + { + /// + /// Gets the provider history. + /// + /// The item identifier. + /// IEnumerable{BaseProviderInfo}. + IEnumerable GetProviderHistory(Guid itemId); + + /// + /// Saves the provider history. + /// + /// The identifier. + /// The history. + /// The cancellation token. + /// Task. + Task SaveProviderHistory(Guid id, IEnumerable history, CancellationToken cancellationToken); + + /// + /// Gets the metadata status. + /// + /// The item identifier. + /// MetadataStatus. + MetadataStatus GetMetadataStatus(Guid itemId); + + /// + /// Saves the metadata status. + /// + /// The status. + /// The cancellation token. + /// Task. + Task SaveMetadataStatus(MetadataStatus status, CancellationToken cancellationToken); + + /// + /// Initializes this instance. + /// + /// Task. + Task Initialize(); + } +} diff --git a/MediaBrowser.Controller/Providers/ItemId.cs b/MediaBrowser.Controller/Providers/ItemId.cs new file mode 100644 index 0000000000..1116eb8b5d --- /dev/null +++ b/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 + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// + /// Gets or sets the metadata language. + /// + /// The metadata language. + public string MetadataLanguage { get; set; } + /// + /// Gets or sets the metadata country code. + /// + /// The metadata country code. + public string MetadataCountryCode { get; set; } + /// + /// Gets or sets the provider ids. + /// + /// The provider ids. + public Dictionary ProviderIds { get; set; } + + public ItemId() + { + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/MediaBrowser.Controller/Providers/MetadataStatus.cs b/MediaBrowser.Controller/Providers/MetadataStatus.cs new file mode 100644 index 0000000000..834d8ec35f --- /dev/null +++ b/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 + { + /// + /// Gets or sets the item identifier. + /// + /// The item identifier. + public Guid ItemId { get; set; } + + /// + /// Gets or sets the date last metadata refresh. + /// + /// The date last metadata refresh. + public DateTime? DateLastMetadataRefresh { get; set; } + + /// + /// Gets or sets the date last images refresh. + /// + /// The date last images refresh. + public DateTime? DateLastImagesRefresh { get; set; } + + /// + /// Gets or sets the last result. + /// + /// The last result. + public ProviderRefreshStatus LastStatus { get; set; } + + /// + /// Gets or sets the last result error message. + /// + /// The last result error message. + public string LastErrorMessage { get; set; } + + /// + /// Gets or sets the providers refreshed. + /// + /// The providers refreshed. + public List MetadataProvidersRefreshed { get; set; } + public List 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(); + ImageProvidersRefreshed = new List(); + } + + 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 providerIds) + { + var count = ImageProvidersRefreshed.Count; + + providerIds.AddRange(ImageProvidersRefreshed); + + ImageProvidersRefreshed = providerIds.Distinct().ToList(); + + if (ImageProvidersRefreshed.Count != count) + { + IsDirty = true; + } + } + + public void AddMetadataProvidersRefreshed(List providerIds) + { + var count = MetadataProvidersRefreshed.Count; + + providerIds.AddRange(MetadataProvidersRefreshed); + + MetadataProvidersRefreshed = providerIds.Distinct().ToList(); + + if (MetadataProvidersRefreshed.Count != count) + { + IsDirty = true; + } + } + } +} diff --git a/MediaBrowser.Controller/Providers/ProviderResult.cs b/MediaBrowser.Controller/Providers/ProviderResult.cs deleted file mode 100644 index 2486faeedc..0000000000 --- a/MediaBrowser.Controller/Providers/ProviderResult.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; - -namespace MediaBrowser.Controller.Providers -{ - public class ProviderResult - { - /// - /// Gets or sets the item identifier. - /// - /// The item identifier. - public Guid ItemId { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has refreshed metadata. - /// - /// true if this instance has refreshed metadata; otherwise, false. - public bool HasRefreshedMetadata { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has refreshed images. - /// - /// true if this instance has refreshed images; otherwise, false. - public bool HasRefreshedImages { get; set; } - - /// - /// Gets or sets the date last refreshed. - /// - /// The date last refreshed. - public DateTime DateLastRefreshed { get; set; } - - /// - /// Gets or sets the last result. - /// - /// The last result. - public ProviderRefreshStatus Status { get; set; } - - /// - /// Gets or sets the last result error message. - /// - /// The last result error message. - 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; - } - } -} diff --git a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs index 7625cdd5a7..389e2a2755 100644 --- a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs +++ b/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; } diff --git a/MediaBrowser.Providers/Genres/GenreMetadataService.cs b/MediaBrowser.Providers/Genres/GenreMetadataService.cs index 7a8ffa5d44..83253a190f 100644 --- a/MediaBrowser.Providers/Genres/GenreMetadataService.cs +++ b/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; } diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 5358506f0e..d455a08dbe 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/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(); + foreach (var provider in providers.OfType()) { await RefreshFromProvider(item, provider, options, result, cancellationToken).ConfigureAwait(false); + + providerIds.Add(provider.GetType().FullName.GetMD5()); } foreach (var provider in providers.OfType()) { await RefreshFromProvider(item, provider, result, cancellationToken).ConfigureAwait(false); + + providerIds.Add(provider.GetType().FullName.GetMD5()); } + result.Providers = providerIds; + return result; } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 5dde3098ac..7916d7e86d 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/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[] _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; } /// @@ -48,9 +51,9 @@ namespace MediaBrowser.Providers.Manager /// /// The result. /// Task. - protected Task SaveProviderResult(ProviderResult result) + protected Task SaveProviderResult(MetadataStatus result) { - return Task.FromResult(true); + return _providerRepo.SaveMetadataStatus(result, CancellationToken.None); } /// @@ -58,12 +61,9 @@ namespace MediaBrowser.Providers.Manager /// /// The item identifier. /// ProviderResult. - 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() - .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 RefreshWithProviders(TItemType item, MetadataRefreshOptions options, List 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 Providers { get; set; } } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index faad156698..3696bd02f5 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/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 /// The directory watchers. /// The log manager. /// The file system. - /// The item repo. - public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo) + /// The provider repo. + 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; } /// @@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Manager var providerHistories = item.DateLastSaved == default(DateTime) ? new List() : - _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; diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs index bc4a99fc3f..b88ca92bc6 100644 --- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs +++ b/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; } diff --git a/MediaBrowser.Providers/People/PersonMetadataService.cs b/MediaBrowser.Providers/People/PersonMetadataService.cs index 577158aa8b..e040139348 100644 --- a/MediaBrowser.Providers/People/PersonMetadataService.cs +++ b/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; } diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs index 1a74d8317f..1a35b94b37 100644 --- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs +++ b/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; } diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 22ea668f48..0716a3d83f 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -56,21 +56,6 @@ namespace MediaBrowser.Server.Implementations.IO _tempIgnoredPaths[path] = path; } - /// - /// Removes the temp ignore. - /// - /// The path. - 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) { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 200898a623..6b463bbdfc 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/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); } /// @@ -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 GetProviderHistory(Guid itemId) - { - return _providerInfoRepository.GetBaseProviderInfos(itemId); - } - - public Task SaveProviderHistory(Guid id, IEnumerable history, CancellationToken cancellationToken) - { - return _providerInfoRepository.SaveProviderInfos(id, history, cancellationToken); - } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs index 9971c74605..8a82c062dd 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs +++ b/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; - + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name + { + get + { + return "SQLite"; + } + } + /// /// Opens the connection to the database /// /// Task. - 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" + }; /// /// 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 GetBaseProviderInfos(Guid itemId) + public IEnumerable 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 } /// - /// Gets the chapter. + /// Gets the base provider information. /// /// The reader. - /// ChapterInfo. + /// BaseProviderInfo. 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 infos, CancellationToken cancellationToken) + public async Task SaveProviderHistory(Guid id, IEnumerable 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(); + } + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index bf652d8cb6..4b15ca8d03 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/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; } /// /// Initializes a new instance of the 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(() => 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; } -- cgit v1.2.3 From c0f606683a045e463f518ec466b9fc9a85f8d4fd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 29 Jan 2014 11:16:24 -0500 Subject: convert programs and channels to new providers --- .../Providers/ILocalImageProvider.cs | 18 ++- MediaBrowser.Providers/BaseXmlProvider.cs | 6 + .../LiveTv/ChannelMetadataService.cs | 37 ++++++ .../LiveTv/ChannelProviderFromXml.cs | 91 --------------- .../LiveTv/ChannelXmlProvider.cs | 59 ++++++++++ .../LiveTv/ProgramMetadataService.cs | 41 +++++++ .../Manager/ItemImageProvider.cs | 23 ++-- .../MediaBrowser.Providers.csproj | 4 +- MediaBrowser.Providers/People/PersonXmlProvider.cs | 5 - .../Library/LibraryManager.cs | 6 +- .../LiveTv/ChannelImageProvider.cs | 129 +++++++-------------- .../LiveTv/ProgramImageProvider.cs | 129 +++++++-------------- 12 files changed, 257 insertions(+), 291 deletions(-) create mode 100644 MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs delete mode 100644 MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs create mode 100644 MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs create mode 100644 MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs index 30f2136059..5c3ebd9acf 100644 --- a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs +++ b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Controller.Providers @@ -28,19 +29,20 @@ namespace MediaBrowser.Controller.Providers public interface IDynamicImageProvider : ILocalImageProvider { /// - /// Gets the images. + /// Gets the supported images. /// /// The item. - /// List{DynamicImageInfo}. - List GetImageInfos(IHasImages item); + /// IEnumerable{ImageType}. + IEnumerable GetSupportedImages(IHasImages item); /// /// Gets the image. /// /// The item. - /// The information. + /// The type. + /// The cancellation token. /// Task{DynamicImageResponse}. - Task GetImage(IHasImages item, DynamicImageInfo info); + Task GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken); } public class DynamicImageInfo @@ -54,5 +56,11 @@ namespace MediaBrowser.Controller.Providers public string Path { get; set; } public Stream Stream { get; set; } public ImageFormat Format { get; set; } + public bool HasImage { get; set; } + + public void SetFormatFromMimeType(string mimeType) + { + + } } } diff --git a/MediaBrowser.Providers/BaseXmlProvider.cs b/MediaBrowser.Providers/BaseXmlProvider.cs index 71043df514..eab5bb574e 100644 --- a/MediaBrowser.Providers/BaseXmlProvider.cs +++ b/MediaBrowser.Providers/BaseXmlProvider.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Providers; using System; +using System.IO; using System.Threading; namespace MediaBrowser.Providers @@ -24,5 +25,10 @@ namespace MediaBrowser.Providers return FileSystem.GetLastWriteTimeUtc(path) > date; } + + public bool HasLocalMetadata(IHasMetadata item) + { + return File.Exists(GetXmlPath(item.Path)); + } } } diff --git a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs new file mode 100644 index 0000000000..dd44ba7aae --- /dev/null +++ b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs @@ -0,0 +1,37 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.LiveTv +{ + public class ChannelMetadataService : MetadataService + { + private readonly ILibraryManager _libraryManager; + + public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo) + { + _libraryManager = libraryManager; + } + + /// + /// Merges the specified source. + /// + protected override void MergeData(LiveTvChannel source, LiveTvChannel target, List lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + protected override Task SaveItem(LiveTvChannel item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _libraryManager.UpdateItem(item, reason, cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs deleted file mode 100644 index 8ee2553d03..0000000000 --- a/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs +++ /dev/null @@ -1,91 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Logging; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.LiveTv -{ - class ChannelProviderFromXml : BaseMetadataProvider - { - private readonly IFileSystem _fileSystem; - - public ChannelProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - : base(logManager, configurationManager) - { - _fileSystem = fileSystem; - } - - /// - /// Supportses the specified item. - /// - /// The item. - /// true if XXXX, false otherwise - public override bool Supports(BaseItem item) - { - return item is LiveTvChannel; - } - - /// - /// Gets the priority. - /// - /// The priority. - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.Second; } - } - - private const string XmlFileName = "channel.xml"; - protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) - { - var xml = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName)); - - if (xml == null) - { - return false; - } - - return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved; - } - - /// - /// 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 metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName)); - - if (metadataFile != null) - { - var path = metadataFile.FullName; - - await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - new BaseItemXmlParser(Logger).Fetch((LiveTvChannel)item, path, cancellationToken); - } - finally - { - XmlParsingResourcePool.Release(); - } - - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - return false; - } - } -} diff --git a/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs b/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs new file mode 100644 index 0000000000..544685f661 --- /dev/null +++ b/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs @@ -0,0 +1,59 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.LiveTv +{ + public class ChannelXmlProvider : BaseXmlProvider, ILocalMetadataProvider + { + private readonly ILogger _logger; + + public ChannelXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + public async Task> GetMetadata(string path, CancellationToken cancellationToken) + { + path = GetXmlPath(path); + + var result = new MetadataResult(); + + await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + var item = new LiveTvChannel(); + + new BaseItemXmlParser(_logger).Fetch(item, path, cancellationToken); + result.HasMetadata = true; + result.Item = item; + } + catch (FileNotFoundException) + { + result.HasMetadata = false; + } + finally + { + XmlParsingResourcePool.Release(); + } + + return result; + } + + public string Name + { + get { return "Media Browser Xml"; } + } + + protected override string GetXmlPath(string path) + { + return Path.Combine(path, "channel.xml"); + } + } +} diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs new file mode 100644 index 0000000000..da032eb8f0 --- /dev/null +++ b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs @@ -0,0 +1,41 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Providers.Manager; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.LiveTv +{ + public class ProgramMetadataService : MetadataService + { + private readonly ILibraryManager _libraryManager; + + public ProgramMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, providerRepo) + { + _libraryManager = libraryManager; + } + + /// + /// Merges the specified source. + /// + /// The source. + /// The target. + /// The locked fields. + /// if set to true [replace data]. + protected override void MergeData(LiveTvProgram source, LiveTvProgram target, List lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + protected override Task SaveItem(LiveTvProgram item, ItemUpdateType reason, CancellationToken cancellationToken) + { + return _libraryManager.UpdateItem(item, reason, cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index d455a08dbe..e8bae1d2fa 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -87,27 +87,22 @@ namespace MediaBrowser.Providers.Manager try { - var images = provider.GetImageInfos(item); + var images = provider.GetSupportedImages(item); - foreach (var image in images) + foreach (var imageType in images) { - if (!item.HasImage(image.Type)) + if (!item.HasImage(imageType)) { - var imageSource = await provider.GetImage(item, image).ConfigureAwait(false); + var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false); - // See if the provider returned an image path or a stream - if (!string.IsNullOrEmpty(imageSource.Path)) + if (response.HasImage) { - item.SetImagePath(image.Type, imageSource.Path); - } - else - { - var mimeType = "image/" + imageSource.Format.ToString().ToLower(); + var mimeType = "image/" + response.Format.ToString().ToLower(); - await _providerManager.SaveImage((BaseItem)item, imageSource.Stream, mimeType, image.Type, null, Guid.NewGuid().ToString(), cancellationToken).ConfigureAwait(false); - } + await _providerManager.SaveImage((BaseItem)item, response.Stream, mimeType, imageType, null, Guid.NewGuid().ToString(), cancellationToken).ConfigureAwait(false); - result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate; + result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate; + } } } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index db8de8a165..b44d3608e1 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -67,6 +67,9 @@ + + + @@ -84,7 +87,6 @@ - diff --git a/MediaBrowser.Providers/People/PersonXmlProvider.cs b/MediaBrowser.Providers/People/PersonXmlProvider.cs index 9da53e8278..900b7956c8 100644 --- a/MediaBrowser.Providers/People/PersonXmlProvider.cs +++ b/MediaBrowser.Providers/People/PersonXmlProvider.cs @@ -55,10 +55,5 @@ namespace MediaBrowser.Providers.People { return Path.Combine(path, "person.xml"); } - - public bool HasLocalMetadata(IHasMetadata item) - { - return File.Exists(GetXmlPath(item.Path)); - } } } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 04344553fc..17b5ea4244 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -1462,13 +1462,13 @@ namespace MediaBrowser.Server.Implementations.Library var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1)); - var directoryWatchers = _libraryMonitorFactory(); + var libraryMonitor = _libraryMonitorFactory(); await semaphore.WaitAsync().ConfigureAwait(false); try { - directoryWatchers.ReportFileSystemChangeBeginning(path); + libraryMonitor.ReportFileSystemChangeBeginning(path); saver.Save(item, CancellationToken.None); } catch (Exception ex) @@ -1477,7 +1477,7 @@ namespace MediaBrowser.Server.Implementations.Library } finally { - directoryWatchers.ReportFileSystemChangeComplete(path, false); + libraryMonitor.ReportFileSystemChangeComplete(path, false); semaphore.Release(); } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs index c1d8d796b9..9fdca568e6 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs @@ -1,154 +1,111 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; using System; -using System.IO; +using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.LiveTv { - public class ChannelImageProvider : BaseMetadataProvider + public class ChannelImageProvider : IDynamicImageProvider, IHasChangeMonitor { private readonly ILiveTvManager _liveTvManager; - private readonly IProviderManager _providerManager; - private readonly IFileSystem _fileSystem; private readonly IHttpClient _httpClient; + private readonly ILogger _logger; - public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient) - : base(logManager, configurationManager) + public ChannelImageProvider(ILiveTvManager liveTvManager, IHttpClient httpClient, ILogger logger) { _liveTvManager = liveTvManager; - _providerManager = providerManager; - _fileSystem = fileSystem; _httpClient = httpClient; + _logger = logger; } - public override bool Supports(BaseItem item) + public IEnumerable GetSupportedImages(IHasImages item) { - return item is LiveTvChannel; - } - - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - return !item.HasImage(ImageType.Primary); + return new[] { ImageType.Primary }; } - public override async Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + public async Task GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) { - if (item.HasImage(ImageType.Primary)) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - var changed = true; - - try - { - changed = await DownloadImage((LiveTvChannel)item, cancellationToken).ConfigureAwait(false); - } - catch (HttpException ex) - { - // Don't fail the provider on a 404 - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } - } - - if (changed) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - } - - return changed; - } + var liveTvItem = (LiveTvChannel)item; - private async Task DownloadImage(LiveTvChannel item, CancellationToken cancellationToken) - { - Stream imageStream = null; - string contentType = null; + var imageResponse = new DynamicImageResponse(); - if (!string.IsNullOrEmpty(item.ProviderImagePath)) + if (!string.IsNullOrEmpty(liveTvItem.ProviderImagePath)) { - contentType = "image/" + Path.GetExtension(item.ProviderImagePath).ToLower(); - imageStream = _fileSystem.GetFileStream(item.ProviderImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); + imageResponse.Path = liveTvItem.ProviderImagePath; + imageResponse.HasImage = true; } - else if (!string.IsNullOrEmpty(item.ProviderImageUrl)) + else if (!string.IsNullOrEmpty(liveTvItem.ProviderImageUrl)) { var options = new HttpRequestOptions { CancellationToken = cancellationToken, - Url = item.ProviderImageUrl + Url = liveTvItem.ProviderImageUrl }; var response = await _httpClient.GetResponse(options).ConfigureAwait(false); - if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) { - Logger.Error("Provider did not return an image content type."); - return false; + imageResponse.HasImage = true; + imageResponse.Stream = response.Content; + imageResponse.SetFormatFromMimeType(response.ContentType); + } + else + { + _logger.Error("Provider did not return an image content type."); } - - imageStream = response.Content; - contentType = response.ContentType; } - else if (item.HasProviderImage ?? true) + else if (liveTvItem.HasProviderImage ?? true) { - var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase)); + var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase)); if (service != null) { try { - var response = await service.GetChannelImageAsync(item.ExternalId, cancellationToken).ConfigureAwait(false); + var response = await service.GetChannelImageAsync(liveTvItem.ExternalId, cancellationToken).ConfigureAwait(false); if (response != null) { - imageStream = response.Stream; - contentType = "image/" + response.Format.ToString().ToLower(); + imageResponse.HasImage = true; + imageResponse.Stream = response.Stream; + imageResponse.Format = response.Format; } } catch (NotImplementedException) { - return false; } } } - if (imageStream != null) - { - // Dummy up the original url - var url = item.ServiceName + item.ExternalId; + return imageResponse; + } - await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); - return true; - } + public string Name + { + get { return "Live TV Service Provider"; } + } - return false; + public bool Supports(IHasImages item) + { + return item is LiveTvChannel; } - public override MetadataProviderPriority Priority + public int Order { - get { return MetadataProviderPriority.Second; } + get { return 0; } } - public override ItemUpdateType ItemUpdateType + public bool HasChanged(IHasMetadata item, DateTime date) { - get - { - return ItemUpdateType.ImageUpdate; - } + return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalDays >= 1; } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs index 3bf2712d43..117cb1da70 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -1,154 +1,111 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; using System; -using System.IO; +using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.LiveTv { - public class ProgramImageProvider : BaseMetadataProvider + public class ProgramImageProvider : IDynamicImageProvider, IHasChangeMonitor { private readonly ILiveTvManager _liveTvManager; - private readonly IProviderManager _providerManager; - private readonly IFileSystem _fileSystem; private readonly IHttpClient _httpClient; + private readonly ILogger _logger; - public ProgramImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient) - : base(logManager, configurationManager) + public ProgramImageProvider(ILiveTvManager liveTvManager, IHttpClient httpClient, ILogger logger) { _liveTvManager = liveTvManager; - _providerManager = providerManager; - _fileSystem = fileSystem; _httpClient = httpClient; + _logger = logger; } - public override bool Supports(BaseItem item) + public IEnumerable GetSupportedImages(IHasImages item) { - return item is LiveTvProgram; - } - - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - return !item.HasImage(ImageType.Primary); + return new[] { ImageType.Primary }; } - public override async Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + public async Task GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) { - if (item.HasImage(ImageType.Primary)) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - return true; - } - - var changed = true; - - try - { - changed = await DownloadImage((LiveTvProgram)item, cancellationToken).ConfigureAwait(false); - } - catch (HttpException ex) - { - // Don't fail the provider on a 404 - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } - } - - if (changed) - { - SetLastRefreshed(item, DateTime.UtcNow, providerInfo); - } - - return changed; - } + var liveTvItem = (LiveTvProgram)item; - private async Task DownloadImage(LiveTvProgram item, CancellationToken cancellationToken) - { - Stream imageStream = null; - string contentType = null; + var imageResponse = new DynamicImageResponse(); - if (!string.IsNullOrEmpty(item.ProviderImagePath)) + if (!string.IsNullOrEmpty(liveTvItem.ProviderImagePath)) { - contentType = "image/" + Path.GetExtension(item.ProviderImagePath).ToLower(); - imageStream = _fileSystem.GetFileStream(item.ProviderImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); + imageResponse.Path = liveTvItem.ProviderImagePath; + imageResponse.HasImage = true; } - else if (!string.IsNullOrEmpty(item.ProviderImageUrl)) + else if (!string.IsNullOrEmpty(liveTvItem.ProviderImageUrl)) { var options = new HttpRequestOptions { CancellationToken = cancellationToken, - Url = item.ProviderImageUrl + Url = liveTvItem.ProviderImageUrl }; var response = await _httpClient.GetResponse(options).ConfigureAwait(false); - if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) { - Logger.Error("Provider did not return an image content type."); - return false; + imageResponse.HasImage = true; + imageResponse.Stream = response.Content; + imageResponse.SetFormatFromMimeType(response.ContentType); + } + else + { + _logger.Error("Provider did not return an image content type."); } - - imageStream = response.Content; - contentType = response.ContentType; } - else if (item.HasProviderImage ?? true) + else if (liveTvItem.HasProviderImage ?? true) { - var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase)); + var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase)); if (service != null) { try { - var response = await service.GetProgramImageAsync(item.ExternalId, item.ExternalChannelId, cancellationToken).ConfigureAwait(false); + var response = await service.GetProgramImageAsync(liveTvItem.ExternalId, liveTvItem.ExternalChannelId, cancellationToken).ConfigureAwait(false); if (response != null) { - imageStream = response.Stream; - contentType = "image/" + response.Format.ToString().ToLower(); + imageResponse.HasImage = true; + imageResponse.Stream = response.Stream; + imageResponse.Format = response.Format; } } catch (NotImplementedException) { - return false; } } } - if (imageStream != null) - { - // Dummy up the original url - var url = item.ServiceName + item.ExternalId; + return imageResponse; + } - await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); - return true; - } + public string Name + { + get { return "Live TV Service Provider"; } + } - return false; + public bool Supports(IHasImages item) + { + return item is LiveTvProgram; } - public override MetadataProviderPriority Priority + public int Order { - get { return MetadataProviderPriority.Second; } + get { return 0; } } - public override ItemUpdateType ItemUpdateType + public bool HasChanged(IHasMetadata item, DateTime date) { - get - { - return ItemUpdateType.ImageUpdate; - } + return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 12; } } } -- cgit v1.2.3