From d609d0edec97655932e365309a847b525a5a7d3d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 7 Nov 2013 10:56:35 -0500 Subject: [PATCH] fixes #616 - Support images in collection folders --- .../Providers/BaseMetadataProvider.cs | 33 +++- .../CollectionFolderImageProvider.cs | 55 ++++++ .../ImageFromMediaLocationProvider.cs | 40 ++++- .../ImagesByNameProvider.cs | 156 ++++-------------- .../MediaBrowser.Providers.csproj | 1 + 5 files changed, 149 insertions(+), 136 deletions(-) create mode 100644 MediaBrowser.Providers/CollectionFolderImageProvider.cs diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs index de7e8e98d..07bb7d5b2 100644 --- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -24,6 +24,7 @@ namespace MediaBrowser.Controller.Providers /// /// The logger. protected ILogger Logger { get; set; } + protected ILogManager LogManager { get; set; } /// @@ -41,6 +42,7 @@ namespace MediaBrowser.Controller.Providers /// The true task result /// protected static readonly Task TrueTaskResult = Task.FromResult(true); + protected static readonly Task FalseTaskResult = Task.FromResult(false); protected static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(5, 5); @@ -132,7 +134,8 @@ namespace MediaBrowser.Controller.Providers /// The provider version. /// The status. /// item - public virtual void SetLastRefreshed(BaseItem item, DateTime value, string providerVersion, ProviderRefreshStatus status = ProviderRefreshStatus.Success) + public virtual void SetLastRefreshed(BaseItem item, DateTime value, string providerVersion, + ProviderRefreshStatus status = ProviderRefreshStatus.Success) { if (item == null) { @@ -172,7 +175,8 @@ namespace MediaBrowser.Controller.Providers /// The item. /// The value. /// The status. - public void SetLastRefreshed(BaseItem item, DateTime value, ProviderRefreshStatus status = ProviderRefreshStatus.Success) + public void SetLastRefreshed(BaseItem item, DateTime value, + ProviderRefreshStatus status = ProviderRefreshStatus.Success) { SetLastRefreshed(item, value, ProviderVersion, status); } @@ -238,7 +242,8 @@ namespace MediaBrowser.Controller.Providers return true; } - if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem && HasFileSystemStampChanged(item, providerInfo)) + if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem && + HasFileSystemStampChanged(item, providerInfo)) { return true; } @@ -349,21 +354,23 @@ namespace MediaBrowser.Controller.Providers } private Dictionary _fileStampExtensionsDictionary; + private Dictionary FileStampExtensionsDictionary { get { return _fileStampExtensionsDictionary ?? (_fileStampExtensionsDictionary = - FilestampExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase)); + FilestampExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase)); } } + /// /// Gets the file system stamp. /// /// The item. /// Guid. - private Guid GetFileSystemStamp(BaseItem item) + protected virtual Guid GetFileSystemStamp(BaseItem item) { // If there's no path or the item is a file, there's nothing to do if (item.LocationType != LocationType.FileSystem) @@ -404,6 +411,20 @@ namespace MediaBrowser.Controller.Providers private static readonly Dictionary FoldersToMonitor = new[] { "extrafanart", "extrathumbs" } .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); + protected Guid GetFileSystemStamp(IEnumerable files) + { + var sb = new StringBuilder(); + + var extensions = FileStampExtensionsDictionary; + var numExtensions = FilestampExtensions.Length; + + // Record the name of each file + // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order + AddFiles(sb, files, extensions, numExtensions); + + return sb.ToString().GetMD5(); + } + /// /// Adds the files. /// @@ -424,7 +445,7 @@ namespace MediaBrowser.Controller.Providers { sb.Append(file.Name); - var children = ((DirectoryInfo) file).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList(); + var children = ((DirectoryInfo)file).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList(); AddFiles(sb, children, extensions, numExtensions); } } diff --git a/MediaBrowser.Providers/CollectionFolderImageProvider.cs b/MediaBrowser.Providers/CollectionFolderImageProvider.cs new file mode 100644 index 000000000..45b1b36c2 --- /dev/null +++ b/MediaBrowser.Providers/CollectionFolderImageProvider.cs @@ -0,0 +1,55 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Providers +{ + public class CollectionFolderImageProvider : ImageFromMediaLocationProvider + { + public CollectionFolderImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) + : base(logManager, configurationManager, fileSystem) + { + } + + public override bool Supports(BaseItem item) + { + return item is CollectionFolder && item.LocationType == LocationType.FileSystem; + } + + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Second; } + } + + protected override FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension) + { + return item.ResolveArgs.PhysicalLocations + .Select(i => GetImageFromLocation(i, filenameWithoutExtension)) + .FirstOrDefault(i => i != null); + } + + protected override Guid GetFileSystemStamp(BaseItem item) + { + var files = item.ResolveArgs.PhysicalLocations + .Select(i => new DirectoryInfo(i)) + .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly)) + .Where(i => + { + var ext = i.Extension; + + return !string.IsNullOrEmpty(ext) && + BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + }) + .ToList(); + + return GetFileSystemStamp(files); + } + } +} diff --git a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs index 9943c7ab8..aa0dcc9d4 100644 --- a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -21,9 +22,12 @@ namespace MediaBrowser.Providers /// public class ImageFromMediaLocationProvider : BaseMetadataProvider { - public ImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager) + protected readonly IFileSystem FileSystem; + + public ImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) : base(logManager, configurationManager) { + FileSystem = fileSystem; } public override ItemUpdateType ItemUpdateType @@ -543,5 +547,37 @@ namespace MediaBrowser.Providers item.ScreenshotImagePaths = screenshotFiles; } } + + protected FileSystemInfo GetImageFromLocation(string path, string filenameWithoutExtension) + { + try + { + var files = new DirectoryInfo(path) + .EnumerateFiles() + .Where(i => + { + var fileName = Path.GetFileNameWithoutExtension(i.FullName); + + if (!string.Equals(fileName, filenameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var ext = i.Extension; + + return !string.IsNullOrEmpty(ext) && + BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + }) + .ToList(); + + return BaseItem.SupportedImageExtensions + .Select(ext => files.FirstOrDefault(i => string.Equals(ext, i.Extension, StringComparison.OrdinalIgnoreCase))) + .FirstOrDefault(file => file != null); + } + catch (DirectoryNotFoundException) + { + return null; + } + } } } diff --git a/MediaBrowser.Providers/ImagesByNameProvider.cs b/MediaBrowser.Providers/ImagesByNameProvider.cs index 6be4ee108..590430823 100644 --- a/MediaBrowser.Providers/ImagesByNameProvider.cs +++ b/MediaBrowser.Providers/ImagesByNameProvider.cs @@ -1,16 +1,12 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace MediaBrowser.Providers { @@ -19,22 +15,11 @@ namespace MediaBrowser.Providers /// public class ImagesByNameProvider : ImageFromMediaLocationProvider { - private readonly IFileSystem _fileSystem; - public ImagesByNameProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) - : base(logManager, configurationManager) + : base(logManager, configurationManager, fileSystem) { - _fileSystem = fileSystem; } - public override ItemUpdateType ItemUpdateType - { - get - { - return ItemUpdateType.ImageUpdate; - } - } - /// /// Supportses the specified item. /// @@ -42,8 +27,8 @@ namespace MediaBrowser.Providers /// true if XXXX, false otherwise public override bool Supports(BaseItem item) { - //only run for these generic types since we are expensive in file i/o - return item is IndexFolder || item is BasePluginFolder || item is CollectionFolder; + // Only run for these generic types since we are expensive in file i/o + return item is BasePluginFolder || item is CollectionFolder; } /// @@ -58,95 +43,6 @@ namespace MediaBrowser.Providers } } - /// - /// Needses the refresh internal. - /// - /// The item. - /// The provider info. - /// true if XXXX, false otherwise - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - // Force a refresh if the IBN path changed - if (providerInfo.FileStamp != ConfigurationManager.ApplicationPaths.ItemsByNamePath.GetMD5()) - { - return true; - } - - return base.NeedsRefreshInternal(item, providerInfo); - } - - /// - /// Gets a value indicating whether [refresh on file system stamp change]. - /// - /// true if [refresh on file system stamp change]; otherwise, false. - protected override bool RefreshOnFileSystemStampChange - { - get - { - return false; - } - } - - /// - /// Override this to return the date that should be compared to the last refresh date - /// to determine if this provider should be re-fetched. - /// - /// The item. - /// DateTime. - protected override DateTime CompareDate(BaseItem item) - { - // If the IBN location exists return the last modified date of any file in it - var location = GetLocation(item); - - var directoryInfo = new DirectoryInfo(location); - - if (!directoryInfo.Exists) - { - return DateTime.MinValue; - } - - var files = directoryInfo.EnumerateFiles().ToList(); - - if (files.Count == 0) - { - return DateTime.MinValue; - } - - return files.Select(f => - { - var lastWriteTime = _fileSystem.GetLastWriteTimeUtc(f); - var creationTime = _fileSystem.GetCreationTimeUtc(f); - - return creationTime > lastWriteTime ? creationTime : lastWriteTime; - - }).Max(); - } - - /// - /// 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, CancellationToken cancellationToken) - { - var result = await base.FetchAsync(item, force, cancellationToken).ConfigureAwait(false); - - BaseProviderInfo data; - - if (!item.ProviderData.TryGetValue(Id, out data)) - { - data = new BaseProviderInfo(); - item.ProviderData[Id] = data; - } - - data.FileStamp = ConfigurationManager.ApplicationPaths.ItemsByNamePath.GetMD5(); - SetLastRefreshed(item, DateTime.UtcNow); - - return result; - } - /// /// Gets the location. /// @@ -154,7 +50,7 @@ namespace MediaBrowser.Providers /// System.String. protected string GetLocation(BaseItem item) { - var name = _fileSystem.GetValidFilename(item.Name); + var name = FileSystem.GetValidFilename(item.Name); return Path.Combine(ConfigurationManager.ApplicationPaths.GeneralPath, name); } @@ -170,30 +66,34 @@ namespace MediaBrowser.Providers { var location = GetLocation(item); - var directoryInfo = new DirectoryInfo(location); + return GetImageFromLocation(location, filenameWithoutExtension); + } - if (!directoryInfo.Exists) + protected override Guid GetFileSystemStamp(BaseItem item) + { + var location = GetLocation(item); + + try { - return null; + var files = new DirectoryInfo(location) + .EnumerateFiles("*", SearchOption.TopDirectoryOnly) + .Where(i => + { + var ext = i.Extension; + + return !string.IsNullOrEmpty(ext) && + BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + }) + .ToList(); + + return GetFileSystemStamp(files); } - - var files = directoryInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList(); - - var file = files.FirstOrDefault(i => string.Equals(i.Name, filenameWithoutExtension + ".png", StringComparison.OrdinalIgnoreCase)); - - if (file != null) + catch (DirectoryNotFoundException) { - return file; + // User doesn't have the folder. No need to log or blow up + + return Guid.Empty; } - - file = files.FirstOrDefault(i => string.Equals(i.Name, filenameWithoutExtension + ".jpg", StringComparison.OrdinalIgnoreCase)); - - if (file != null) - { - return file; - } - - return null; } } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 5aa8a0c84..ad5dd9b3f 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -47,6 +47,7 @@ +