diff options
16 files changed, 275 insertions, 135 deletions
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index e9f222016..7b64c0e85 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Entities.Audio { @@ -53,30 +54,6 @@ namespace MediaBrowser.Controller.Entities.Audio } /// <summary> - /// Override to point to first child (song) if not explicitly defined - /// </summary> - /// <value>The primary image path.</value> - [IgnoreDataMember] - public override string PrimaryImagePath - { - get - { - if (base.PrimaryImagePath == null) - { - var child = Children.FirstOrDefault(); - - return child == null ? base.PrimaryImagePath : child.PrimaryImagePath; - } - - return base.PrimaryImagePath; - } - set - { - base.PrimaryImagePath = value; - } - } - - /// <summary> /// Override to point to first child (song) /// </summary> /// <value>The production year.</value> @@ -131,6 +108,39 @@ namespace MediaBrowser.Controller.Entities.Audio } /// <summary> + /// Gets or sets the images. + /// </summary> + /// <value>The images.</value> + public override Dictionary<string, string> Images + { + get + { + var images = base.Images; + string primaryImagePath; + + if (images == null || !images.TryGetValue(ImageType.Primary.ToString(), out primaryImagePath)) + { + var image = Children.Select(c => c.PrimaryImagePath).FirstOrDefault(c => !string.IsNullOrEmpty(c)); + + if (!string.IsNullOrEmpty(image)) + { + if (images == null) + { + images = new Dictionary<string, string>(); + } + images[ImageType.Primary.ToString()] = image; + } + } + + return images; + } + set + { + base.Images = value; + } + } + + /// <summary> /// Creates the name of the sort. /// </summary> /// <returns>System.String.</returns> diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 411190d77..7587a8a5a 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The primary image path.</value> [IgnoreDataMember] - public virtual string PrimaryImagePath + public string PrimaryImagePath { get { return GetImage(ImageType.Primary); } set { SetImage(ImageType.Primary, value); } @@ -79,7 +79,7 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the images. /// </summary> /// <value>The images.</value> - public Dictionary<string, string> Images { get; set; } + public virtual Dictionary<string, string> Images { get; set; } /// <summary> /// Gets or sets the date created. @@ -547,6 +547,12 @@ namespace MediaBrowser.Controller.Entities public virtual List<string> Studios { get; set; } /// <summary> + /// Gets or sets the publishers. + /// </summary> + /// <value>The publishers.</value> + public virtual List<string> Publishers { get; set; } + + /// <summary> /// Gets or sets the genres. /// </summary> /// <value>The genres.</value> @@ -996,7 +1002,7 @@ namespace MediaBrowser.Controller.Entities { if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentNullException(); + throw new ArgumentNullException("name"); } if (Studios == null) @@ -1011,6 +1017,47 @@ namespace MediaBrowser.Controller.Entities } /// <summary> + /// Adds the publishers. + /// </summary> + /// <param name="publishers">The publishers.</param> + /// <exception cref="System.ArgumentNullException"></exception> + public void AddPublishers(IEnumerable<string> publishers) + { + if (publishers == null) + { + throw new ArgumentNullException(); + } + + foreach (var name in publishers) + { + AddPublisher(name); + } + } + + /// <summary> + /// Adds the publisher. + /// </summary> + /// <param name="name">The name.</param> + /// <exception cref="System.ArgumentNullException">name</exception> + public void AddPublisher(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException("name"); + } + + if (Publishers == null) + { + Publishers = new List<string>(); + } + + if (!Publishers.Contains(name, StringComparer.OrdinalIgnoreCase)) + { + Publishers.Add(name); + } + } + + /// <summary> /// Adds a tagline to the item /// </summary> /// <param name="name">The name.</param> @@ -1019,7 +1066,7 @@ namespace MediaBrowser.Controller.Entities { if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentNullException(); + throw new ArgumentNullException("name"); } if (Taglines == null) @@ -1042,7 +1089,7 @@ namespace MediaBrowser.Controller.Entities { if (string.IsNullOrWhiteSpace(url)) { - throw new ArgumentNullException(); + throw new ArgumentNullException("url"); } if (TrailerUrls == null) diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 690f97605..954dfb05f 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -125,7 +125,7 @@ namespace MediaBrowser.Controller.Entities { get { - LazyInitializer.EnsureInitialized(ref _rootFolder, ref _userRootFolderInitialized, ref _userRootFolderSyncLock, () => (UserRootFolder)LibraryManager.ResolvePath(RootFolderPath)); + LazyInitializer.EnsureInitialized(ref _rootFolder, ref _userRootFolderInitialized, ref _userRootFolderSyncLock, () => LibraryManager.GetUserRootFolder(RootFolderPath)); return _rootFolder; } private set @@ -219,22 +219,6 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// Validates only the collection folders for a User and goes no further - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public async Task ValidateCollectionFolders(IProgress<double> progress, CancellationToken cancellationToken) - { - Logger.Info("Validating collection folders for {0}", Name); - await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - await RootFolder.ValidateChildren(progress, cancellationToken, recursive: false).ConfigureAwait(false); - } - - /// <summary> /// Renames the user. /// </summary> /// <param name="newName">The new name.</param> diff --git a/MediaBrowser.Controller/Library/DtoBuilder.cs b/MediaBrowser.Controller/Library/DtoBuilder.cs index f089b531c..1fde6a2d8 100644 --- a/MediaBrowser.Controller/Library/DtoBuilder.cs +++ b/MediaBrowser.Controller/Library/DtoBuilder.cs @@ -64,6 +64,11 @@ namespace MediaBrowser.Controller.Library dto.Studios = item.Studios; } + if (fields.Contains(ItemFields.Publishers)) + { + dto.Publishers = item.Publishers; + } + if (fields.Contains(ItemFields.People)) { tasks.Add(AttachPeople(dto, item)); diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index da2f46abb..36d39d530 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -163,5 +163,12 @@ namespace MediaBrowser.Controller.Library /// <param name="item"></param> /// <returns>The proper instance to the item</returns> BaseItem GetOrAddByReferenceItem(BaseItem item); + + /// <summary> + /// Gets the user root folder. + /// </summary> + /// <param name="userRootPath">The user root path.</param> + /// <returns>UserRootFolder.</returns> + UserRootFolder GetUserRootFolder(string userRootPath); } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 1c03e11dd..8c2895701 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -146,7 +146,6 @@ <Compile Include="Providers\FanartBaseProvider.cs" /> <Compile Include="Providers\IImageEnhancer.cs" /> <Compile Include="Providers\ImagesByNameProvider.cs" /> - <Compile Include="Providers\MediaInfo\BaseFFMpegImageProvider.cs" /> <Compile Include="Providers\MediaInfo\BaseFFMpegProvider.cs" /> <Compile Include="Providers\MediaInfo\FFMpegAudioImageProvider.cs" /> <Compile Include="Providers\MediaInfo\BaseFFProbeProvider.cs" /> diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs index 3a4f3cae7..f0a960ce1 100644 --- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs +++ b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs @@ -735,16 +735,16 @@ namespace MediaBrowser.Controller.MediaInfo /// <summary> /// Extracts an image from an Audio file and returns a Task whose result indicates whether it was successful or not /// </summary> - /// <param name="input">The input.</param> + /// <param name="inputPath">The input path.</param> /// <param name="outputPath">The output path.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> /// <exception cref="System.ArgumentNullException">input</exception> - public async Task<bool> ExtractImage(Audio input, string outputPath, CancellationToken cancellationToken) + public async Task<bool> ExtractAudioImage(string inputPath, string outputPath, CancellationToken cancellationToken) { - if (input == null) + if (string.IsNullOrEmpty(inputPath)) { - throw new ArgumentNullException("input"); + throw new ArgumentNullException("inputPath"); } if (string.IsNullOrEmpty(outputPath)) @@ -759,7 +759,7 @@ namespace MediaBrowser.Controller.MediaInfo CreateNoWindow = true, UseShellExecute = false, FileName = FFMpegPath, - Arguments = string.Format("-i {0} -threads 0 -v quiet -f image2 \"{1}\"", GetInputArgument(input), outputPath), + Arguments = string.Format("-i {0} -threads 0 -v quiet -f image2 \"{1}\"", GetFileInputArgument(inputPath), outputPath), WindowStyle = ProcessWindowStyle.Hidden, ErrorDialog = false } @@ -780,7 +780,7 @@ namespace MediaBrowser.Controller.MediaInfo return true; } - _logger.Error("ffmpeg audio image extraction failed for {0}", input.Path); + _logger.Error("ffmpeg audio image extraction failed for {0}", inputPath); return false; } @@ -1040,6 +1040,16 @@ namespace MediaBrowser.Controller.MediaInfo } /// <summary> + /// Gets the file input argument. + /// </summary> + /// <param name="path">The path.</param> + /// <returns>System.String.</returns> + private string GetFileInputArgument(string path) + { + return string.Format("file:\"{0}\"", path); + } + + /// <summary> /// Gets the input argument. /// </summary> /// <param name="item">The item.</param> diff --git a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegImageProvider.cs deleted file mode 100644 index 902fdca27..000000000 --- a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegImageProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Logging; - -namespace MediaBrowser.Controller.Providers.MediaInfo -{ - public abstract class BaseFFMpegImageProvider<T> : BaseFFMpegProvider<T> - where T : BaseItem - { - protected BaseFFMpegImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) - { - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.Last; } - } - } -} diff --git a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs index 20f59b22d..6edd28b24 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs @@ -2,9 +2,9 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System; using System.Threading.Tasks; -using MediaBrowser.Model.Logging; namespace MediaBrowser.Controller.Providers.MediaInfo { diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs index e752a863d..68e552d3c 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs @@ -1,21 +1,23 @@ -using MediaBrowser.Controller.Configuration; +using System.Collections.Concurrent; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Logging; namespace MediaBrowser.Controller.Providers.MediaInfo { /// <summary> /// Uses ffmpeg to create video images /// </summary> - public class FFMpegAudioImageProvider : BaseFFMpegImageProvider<Audio> + public class FFMpegAudioImageProvider : BaseFFMpegProvider<Audio> { - public FFMpegAudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) + public FFMpegAudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager) + : base(logManager, configurationManager) { } @@ -25,67 +27,101 @@ namespace MediaBrowser.Controller.Providers.MediaInfo protected static readonly Task<bool> TrueTaskResult = Task.FromResult(true); /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Last; } + } + + /// <summary> + /// The _locks + /// </summary> + private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>(); + + /// <summary> + /// Gets the lock. + /// </summary> + /// <param name="filename">The filename.</param> + /// <returns>System.Object.</returns> + private SemaphoreSlim GetLock(string filename) + { + return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + } + + /// <summary> /// Fetches metadata and returns true or false indicating if any work that requires persistence was done /// </summary> /// <param name="item">The item.</param> /// <param name="force">if set to <c>true</c> [force].</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.Boolean}.</returns> - public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) + public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - var audio = (Audio)item; + var success = ProviderRefreshStatus.Success; - if (string.IsNullOrEmpty(audio.PrimaryImagePath)) + if (force || string.IsNullOrEmpty(item.PrimaryImagePath)) { - // First try to use the parent's image - audio.PrimaryImagePath = audio.ResolveArgs.Parent.PrimaryImagePath; + var album = item.ResolveArgs.Parent as MusicAlbum; + + if (album != null) + { + // First try to use the parent's image + item.PrimaryImagePath = item.ResolveArgs.Parent.PrimaryImagePath; + } // If it's still empty see if there's an embedded image - if (string.IsNullOrEmpty(audio.PrimaryImagePath)) + if (force || string.IsNullOrEmpty(item.PrimaryImagePath)) { + var audio = (Audio)item; + if (audio.MediaStreams != null && audio.MediaStreams.Any(s => s.Type == MediaStreamType.Video)) { - var filename = item.Id + "_" + item.DateModified.Ticks + "_primary"; + var filename = album != null && string.IsNullOrEmpty(audio.Album + album.DateModified.Ticks) ? (audio.Id.ToString() + audio.DateModified.Ticks) : audio.Album; - var path = Kernel.Instance.FFMpegManager.AudioImageCache.GetResourcePath(filename, ".jpg"); + var path = Kernel.Instance.FFMpegManager.AudioImageCache.GetResourcePath(filename + "_primary", ".jpg"); if (!Kernel.Instance.FFMpegManager.AudioImageCache.ContainsFilePath(path)) { - return ExtractImage(audio, path, cancellationToken); - } - - // Image is already in the cache - audio.PrimaryImagePath = path; - } + var semaphore = GetLock(path); - } - } + // Acquire a lock + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - SetLastRefreshed(item, DateTime.UtcNow); - return TrueTaskResult; - } + // Check again + if (!Kernel.Instance.FFMpegManager.AudioImageCache.ContainsFilePath(path)) + { + try + { + var imageSucceeded = await Kernel.Instance.FFMpegManager.ExtractAudioImage(audio.Path, path, cancellationToken).ConfigureAwait(false); - /// <summary> - /// Extracts the image. - /// </summary> - /// <param name="audio">The audio.</param> - /// <param name="path">The path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{System.Boolean}.</returns> - private async Task<bool> ExtractImage(Audio audio, string path, CancellationToken cancellationToken) - { - var success = await Kernel.Instance.FFMpegManager.ExtractImage(audio, path, cancellationToken).ConfigureAwait(false); + if (!imageSucceeded) + { + success = ProviderRefreshStatus.Failure; + } + } + finally + { + semaphore.Release(); + } + } + else + { + semaphore.Release(); + } + } - if (success) - { - audio.PrimaryImagePath = path; - SetLastRefreshed(audio, DateTime.UtcNow); - } - else - { - SetLastRefreshed(audio, DateTime.UtcNow, ProviderRefreshStatus.Failure); + if (success == ProviderRefreshStatus.Success) + { + // Image is already in the cache + audio.PrimaryImagePath = path; + } + } + } } + SetLastRefreshed(item, DateTime.UtcNow, success); return true; } } diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs index 3d258fe66..9dec93a8c 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo /// <summary> /// Uses ffmpeg to create video images /// </summary> - public class FfMpegVideoImageProvider : BaseFFMpegImageProvider<Video> + public class FfMpegVideoImageProvider : BaseFFMpegProvider<Video> { /// <summary> /// The _iso manager @@ -30,6 +30,15 @@ namespace MediaBrowser.Controller.Providers.MediaInfo : base(logManager, configurationManager) { _isoManager = isoManager; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Last; } } /// <summary> @@ -80,7 +89,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo /// <returns>Task{System.Boolean}.</returns> public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(item.PrimaryImagePath)) + if (force || string.IsNullOrEmpty(item.PrimaryImagePath)) { var video = (Video)item; diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs index a7cc4985b..e5f57d704 100644 --- a/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs +++ b/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo // There's several values in tags may or may not be present FetchStudios(audio, tags, "organization"); FetchStudios(audio, tags, "ensemble"); - FetchStudios(audio, tags, "publisher"); + FetchPublishers(audio, tags, "publisher"); } /// <summary> @@ -169,6 +169,22 @@ namespace MediaBrowser.Controller.Providers.MediaInfo } /// <summary> + /// Fetches the publishers. + /// </summary> + /// <param name="audio">The audio.</param> + /// <param name="tags">The tags.</param> + /// <param name="tagName">Name of the tag.</param> + private void FetchPublishers(Audio audio, Dictionary<string, string> tags, string tagName) + { + var val = GetDictionaryValue(tags, tagName); + + if (!string.IsNullOrEmpty(val)) + { + audio.AddPublishers(val.Split(new[] { '/', '|' }, StringSplitOptions.RemoveEmptyEntries)); + } + } + + /// <summary> /// Gets the genres from the tags collection /// </summary> /// <param name="audio">The audio.</param> diff --git a/MediaBrowser.Model/DTO/BaseItemDto.cs b/MediaBrowser.Model/DTO/BaseItemDto.cs index b4d6aecd9..dd218b832 100644 --- a/MediaBrowser.Model/DTO/BaseItemDto.cs +++ b/MediaBrowser.Model/DTO/BaseItemDto.cs @@ -432,6 +432,13 @@ namespace MediaBrowser.Model.Dto /// <value>The overview HTML.</value> [ProtoMember(70)] public string OverviewHtml { get; set; } + + /// <summary> + /// Gets or sets the publishers. + /// </summary> + /// <value>The publishers.</value> + [ProtoMember(71)] + public List<string> Publishers { get; set; } /// <summary> /// Gets a value indicating whether this instance can resume. diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index 7e57f8f90..d693b5c53 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -82,6 +82,11 @@ namespace MediaBrowser.Model.Querying PrimaryImageAspectRatio, /// <summary> + /// The publishers + /// </summary> + Publishers, + + /// <summary> /// AirDays, status, SeriesName, etc /// </summary> SeriesInfo, diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index a57eb8eaa..9bc47a025 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -126,6 +126,9 @@ namespace MediaBrowser.Server.Implementations.Library } } + private ConcurrentDictionary<string, UserRootFolder> _userRootFolders = + new ConcurrentDictionary<string, UserRootFolder>(); + /// <summary> /// Initializes a new instance of the <see cref="LibraryManager" /> class. /// </summary> @@ -485,6 +488,16 @@ namespace MediaBrowser.Server.Implementations.Library } /// <summary> + /// Gets the user root folder. + /// </summary> + /// <param name="userRootPath">The user root path.</param> + /// <returns>UserRootFolder.</returns> + public UserRootFolder GetUserRootFolder(string userRootPath) + { + return _userRootFolders.GetOrAdd(userRootPath, key => Kernel.ItemRepository.RetrieveItem(userRootPath.GetMBId(typeof(UserRootFolder))) as UserRootFolder ?? (UserRootFolder)ResolvePath(userRootPath)); + } + + /// <summary> /// Gets a Person /// </summary> /// <param name="name">The name.</param> @@ -739,15 +752,32 @@ namespace MediaBrowser.Server.Implementations.Library // Start by just validating the children of the root, but go no further await RootFolder.ValidateChildren(new Progress<double> { }, cancellationToken, recursive: false); - // Validate only the collection folders for each user, just to make them available as quickly as possible - var userCollectionFolderTasks = _userManager.Users.AsParallel().Select(user => user.ValidateCollectionFolders(new Progress<double> { }, cancellationToken)); - await Task.WhenAll(userCollectionFolderTasks).ConfigureAwait(false); + foreach (var folder in _userManager.Users.Select(u => u.RootFolder).Distinct()) + { + await ValidateCollectionFolders(folder, cancellationToken).ConfigureAwait(false); + } // Now validate the entire media library await RootFolder.ValidateChildren(progress, cancellationToken, recursive: true).ConfigureAwait(false); } /// <summary> + /// Validates only the collection folders for a User and goes no further + /// </summary> + /// <param name="userRootFolder">The user root folder.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task ValidateCollectionFolders(UserRootFolder userRootFolder, CancellationToken cancellationToken) + { + _logger.Info("Validating collection folders within {0}", userRootFolder.Path); + await userRootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + await userRootFolder.ValidateChildren(new Progress<double> { }, cancellationToken, recursive: false).ConfigureAwait(false); + } + + /// <summary> /// Gets the default view. /// </summary> /// <returns>IEnumerable{VirtualFolderInfo}.</returns> diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs index df6e06174..dced1ce28 100644 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -136,26 +136,24 @@ namespace MediaBrowser.Server.Implementations.Providers } var supportedProvidersHash = string.Join("+", supportedProviders.Select(i => i.GetType().Name)).GetMD5(); - bool providersChanged; + bool providersChanged = false; item.ProviderData.TryGetValue(SupportedProvidersKey, out supportedProvidersInfo); - if (supportedProvidersInfo == null) - { - // First time - supportedProvidersInfo = new BaseProviderInfo { ProviderId = SupportedProvidersKey, FileSystemStamp = supportedProvidersHash }; - providersChanged = force = true; - } - else + if (supportedProvidersInfo != null) { // Force refresh if the supported providers have changed providersChanged = force = force || supportedProvidersInfo.FileSystemStamp != supportedProvidersHash; + + // If providers have changed, clear provider info and update the supported providers hash + if (providersChanged) + { + _logger.Debug("Providers changed for {0}. Clearing and forcing refresh.", item.Name); + item.ProviderData.Clear(); + } } - // If providers have changed, clear provider info and update the supported providers hash if (providersChanged) { - _logger.Debug("Providers changed for {0}. Clearing and forcing refresh.", item.Name); - item.ProviderData.Clear(); supportedProvidersInfo.FileSystemStamp = supportedProvidersHash; } |
