diff options
Diffstat (limited to 'MediaBrowser.Controller/Entities/Folder.cs')
| -rw-r--r-- | MediaBrowser.Controller/Entities/Folder.cs | 173 |
1 files changed, 104 insertions, 69 deletions
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 666455cff..8cea8755c 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Progress; @@ -35,45 +36,16 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class Folder : BaseItem { - /// <summary> - /// Contains constants used when reporting scan progress. - /// </summary> - private static class ProgressHelpers - { - /// <summary> - /// Reported after the folders immediate children are retrieved. - /// </summary> - public const int RetrievedChildren = 5; - - /// <summary> - /// Reported after add, updating, or deleting child items from the LibraryManager. - /// </summary> - public const int UpdatedChildItems = 10; - - /// <summary> - /// Reported once subfolders are scanned. - /// When scanning subfolders, the progress will be between [UpdatedItems, ScannedSubfolders]. - /// </summary> - public const int ScannedSubfolders = 50; - - /// <summary> - /// Reported once metadata is refreshed. - /// When refreshing metadata, the progress will be between [ScannedSubfolders, MetadataRefreshed]. - /// </summary> - public const int RefreshedMetadata = 100; + private static Lazy<SemaphoreSlim> _metadataRefreshThrottler = new Lazy<SemaphoreSlim>(() => { + var concurrency = ConfigurationManager.Configuration.LibraryMetadataRefreshConcurrency; - /// <summary> - /// Gets the current progress given the previous step, next step, and progress in between. - /// </summary> - /// <param name="previousProgressStep">The previous progress step.</param> - /// <param name="nextProgressStep">The next progress step.</param> - /// <param name="currentProgress">The current progress step.</param> - /// <returns>The progress.</returns> - public static double GetProgress(int previousProgressStep, int nextProgressStep, double currentProgress) + if (concurrency <= 0) { - return previousProgressStep + ((nextProgressStep - previousProgressStep) * (currentProgress / 100)); + concurrency = Environment.ProcessorCount; } - } + + return new SemaphoreSlim(concurrency); + }); public static IUserViewManager UserViewManager { get; set; } @@ -508,19 +480,17 @@ namespace MediaBrowser.Controller.Entities private Task RefreshMetadataRecursive(IList<BaseItem> children, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) { - var progressableTasks = children - .Select<BaseItem, Func<IProgress<double>, Task>>(child => - innerProgress => RefreshChildMetadata(child, refreshOptions, recursive && child.IsFolder, innerProgress, cancellationToken)) - .ToList(); - - return RunTasks(progressableTasks, progress, cancellationToken); + return RunTasks( + (baseItem, innerProgress) => RefreshChildMetadata(baseItem, refreshOptions, recursive && baseItem.IsFolder, innerProgress, cancellationToken), + children, + progress, + cancellationToken); } private async Task RefreshAllMetadataForContainer(IMetadataContainer container, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken) { // limit the amount of concurrent metadata refreshes - await TaskMethods.RunThrottled( - TaskMethods.SharedThrottleId.RefreshMetadata, + await RunMetadataRefresh( async () => { var series = container as Series; @@ -547,8 +517,7 @@ namespace MediaBrowser.Controller.Entities if (refreshOptions.RefreshItem(child)) { // limit the amount of concurrent metadata refreshes - await TaskMethods.RunThrottled( - TaskMethods.SharedThrottleId.RefreshMetadata, + await RunMetadataRefresh( async () => await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); } @@ -570,38 +539,33 @@ namespace MediaBrowser.Controller.Entities /// <returns>Task.</returns> private Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken) { - var progressableTasks = children - .Select<Folder, Func<IProgress<double>, Task>>(child => - innerProgress => child.ValidateChildrenInternal(innerProgress, cancellationToken, true, false, null, directoryService)) - .ToList(); - - return RunTasks(progressableTasks, progress, cancellationToken); + return RunTasks( + (folder, innerProgress) => folder.ValidateChildrenInternal(innerProgress, cancellationToken, true, false, null, directoryService), + children, + progress, + cancellationToken); } /// <summary> - /// Runs a set of tasks concurrently with progress. + /// Runs an action block on a list of children. /// </summary> - /// <param name="tasks">A list of tasks.</param> + /// <param name="task">The task to run for each child.</param> + /// <param name="children">The list of children.</param> /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private async Task RunTasks(IList<Func<IProgress<double>, Task>> tasks, IProgress<double> progress, CancellationToken cancellationToken) + private async Task RunTasks<T>(Func<T, IProgress<double>, Task> task, IList<T> children, IProgress<double> progress, CancellationToken cancellationToken) { - var childrenCount = tasks.Count; + var childrenCount = children.Count; var childrenProgress = new double[childrenCount]; - var actions = new Func<Task>[childrenCount]; void UpdateProgress() { progress.Report(childrenProgress.Average()); } - for (var i = 0; i < childrenCount; i++) - { - var childIndex = i; - var child = tasks[childIndex]; - - actions[childIndex] = async () => + var actionBlock = new ActionBlock<int>( + async i => { var innerProgress = new ActionableProgress<double>(); @@ -609,22 +573,33 @@ namespace MediaBrowser.Controller.Entities { // round the percent and only update progress if it changed to prevent excessive UpdateProgress calls var innerPercentRounded = Math.Round(innerPercent); - if (childrenProgress[childIndex] != innerPercentRounded) + if (childrenProgress[i] != innerPercentRounded) { - childrenProgress[childIndex] = innerPercentRounded; + childrenProgress[i] = innerPercentRounded; UpdateProgress(); } }); - await tasks[childIndex](innerProgress).ConfigureAwait(false); + await task(children[i], innerProgress).ConfigureAwait(false); - childrenProgress[childIndex] = 100; + childrenProgress[i] = 100; UpdateProgress(); - }; + }, + new ExecutionDataflowBlockOptions + { + MaxDegreeOfParallelism = ConfigurationManager.Configuration.LibraryScanFanoutConcurrency, + CancellationToken = cancellationToken, + }); + + for (var i = 0; i < childrenCount; i++) + { + actionBlock.Post(i); } - await TaskMethods.WhenAllThrottled(TaskMethods.SharedThrottleId.ScanFanout, actions, cancellationToken).ConfigureAwait(false); + actionBlock.Complete(); + + await actionBlock.Completion.ConfigureAwait(false); } /// <summary> @@ -1272,6 +1247,26 @@ namespace MediaBrowser.Controller.Entities return true; } + /// <summary> + /// Runs multiple metadata refreshes concurrently. + /// </summary> + /// <param name="action">The action to run.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns> + private static async Task RunMetadataRefresh(Func<Task> action, CancellationToken cancellationToken) + { + await _metadataRefreshThrottler.Value.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + await action().ConfigureAwait(false); + } + finally + { + _metadataRefreshThrottler.Value.Release(); + } + } + public List<BaseItem> GetChildren(User user, bool includeLinkedChildren) { if (user == null) @@ -1819,5 +1814,45 @@ namespace MediaBrowser.Controller.Entities } } } + + /// <summary> + /// Contains constants used when reporting scan progress. + /// </summary> + private static class ProgressHelpers + { + /// <summary> + /// Reported after the folders immediate children are retrieved. + /// </summary> + public const int RetrievedChildren = 5; + + /// <summary> + /// Reported after add, updating, or deleting child items from the LibraryManager. + /// </summary> + public const int UpdatedChildItems = 10; + + /// <summary> + /// Reported once subfolders are scanned. + /// When scanning subfolders, the progress will be between [UpdatedItems, ScannedSubfolders]. + /// </summary> + public const int ScannedSubfolders = 50; + + /// <summary> + /// Reported once metadata is refreshed. + /// When refreshing metadata, the progress will be between [ScannedSubfolders, MetadataRefreshed]. + /// </summary> + public const int RefreshedMetadata = 100; + + /// <summary> + /// Gets the current progress given the previous step, next step, and progress in between. + /// </summary> + /// <param name="previousProgressStep">The previous progress step.</param> + /// <param name="nextProgressStep">The next progress step.</param> + /// <param name="currentProgress">The current progress step.</param> + /// <returns>The progress.</returns> + public static double GetProgress(int previousProgressStep, int nextProgressStep, double currentProgress) + { + return previousProgressStep + ((nextProgressStep - previousProgressStep) * (currentProgress / 100)); + } + } } } |
