diff options
Diffstat (limited to 'MediaBrowser.Controller/Entities/Folder.cs')
| -rw-r--r-- | MediaBrowser.Controller/Entities/Folder.cs | 283 |
1 files changed, 143 insertions, 140 deletions
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 63a1c2bab..cb14ed099 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,11 +1,9 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MoreLinq; using System; @@ -301,15 +299,27 @@ namespace MediaBrowser.Controller.Entities /// <value>The current validation cancellation token source.</value> private CancellationTokenSource CurrentValidationCancellationTokenSource { get; set; } + public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken) + { + return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions()); + } + /// <summary> /// Validates that the children of the folder still exist /// </summary> /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="metadataRefreshOptions">The metadata refresh options.</param> /// <param name="recursive">if set to <c>true</c> [recursive].</param> - /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param> /// <returns>Task.</returns> - public async Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false) + public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken, MetadataRefreshOptions metadataRefreshOptions, bool recursive = true) + { + metadataRefreshOptions.DirectoryService = metadataRefreshOptions.DirectoryService ?? new DirectoryService(Logger); + + return ValidateChildrenWithCancellationSupport(progress, cancellationToken, recursive, true, metadataRefreshOptions, metadataRefreshOptions.DirectoryService); + } + + private async Task ValidateChildrenWithCancellationSupport(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { cancellationToken.ThrowIfCancellationRequested(); @@ -329,7 +339,7 @@ namespace MediaBrowser.Controller.Entities var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken); - await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, forceRefreshMetadata).ConfigureAwait(false); + await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, refreshChildMetadata, refreshOptions, directoryService).ConfigureAwait(false); } catch (OperationCanceledException ex) { @@ -354,21 +364,22 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes - /// ***Currently does not contain logic to maintain items that are unavailable in the file system*** + /// Validates the children internal. /// </summary> /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="recursive">if set to <c>true</c> [recursive].</param> - /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param> + /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param> + /// <param name="refreshOptions">The refresh options.</param> + /// <param name="directoryService">The directory service.</param> /// <returns>Task.</returns> - protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false) + protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { var locationType = LocationType; cancellationToken.ThrowIfCancellationRequested(); - var validChildren = new List<Tuple<BaseItem, bool>>(); + var validChildren = new List<BaseItem>(); if (locationType != LocationType.Remote && locationType != LocationType.Virtual) { @@ -376,7 +387,7 @@ namespace MediaBrowser.Controller.Entities try { - nonCachedChildren = GetNonCachedChildren(); + nonCachedChildren = GetNonCachedChildren(directoryService); } catch (IOException ex) { @@ -403,43 +414,30 @@ namespace MediaBrowser.Controller.Entities if (currentChildren.TryGetValue(child.Id, out currentChild)) { - //existing item - check if it has changed - if (currentChild.HasChanged(child)) - { - var currentChildLocationType = currentChild.LocationType; - if (currentChildLocationType != LocationType.Remote && - currentChildLocationType != LocationType.Virtual) - { - currentChild.DateModified = child.DateModified; - } - - currentChild.IsInMixedFolder = child.IsInMixedFolder; - validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true)); - } - else + var currentChildLocationType = currentChild.LocationType; + if (currentChildLocationType != LocationType.Remote && + currentChildLocationType != LocationType.Virtual) { - validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false)); + currentChild.DateModified = child.DateModified; } + currentChild.IsInMixedFolder = child.IsInMixedFolder; currentChild.IsOffline = false; } else { //brand new item - needs to be added newItems.Add(child); - - validChildren.Add(new Tuple<BaseItem, bool>(child, true)); } + + validChildren.Add(currentChild); } // If any items were added or removed.... if (newItems.Count > 0 || currentChildren.Count != validChildren.Count) { - var newChildren = validChildren.Select(c => c.Item1).ToList(); - // That's all the new and changed ones - now see if there are any that are missing - var itemsRemoved = currentChildren.Values.Except(newChildren).ToList(); - + var itemsRemoved = currentChildren.Values.Except(validChildren).ToList(); var actualRemovals = new List<BaseItem>(); foreach (var item in itemsRemoved) @@ -448,14 +446,13 @@ namespace MediaBrowser.Controller.Entities item.LocationType == LocationType.Remote) { // Don't remove these because there's no way to accurately validate them. - validChildren.Add(new Tuple<BaseItem, bool>(item, false)); + validChildren.Add(item); } else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path)) { item.IsOffline = true; - - validChildren.Add(new Tuple<BaseItem, bool>(item, false)); + validChildren.Add(item); } else { @@ -481,88 +478,134 @@ namespace MediaBrowser.Controller.Entities await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false); } } - else - { - validChildren.AddRange(ActualChildren.Select(i => new Tuple<BaseItem, bool>(i, false))); - } progress.Report(10); cancellationToken.ThrowIfCancellationRequested(); - await RefreshChildren(validChildren, progress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false); + if (recursive) + { + await ValidateSubFolders(ActualChildren.OfType<Folder>().ToList(), directoryService, progress, cancellationToken).ConfigureAwait(false); + } + + progress.Report(20); + + if (refreshChildMetadata) + { + var container = this as IMetadataContainer; + + var innerProgress = new ActionableProgress<double>(); + + innerProgress.RegisterAction(p => progress.Report((.80 * p) + 20)); + + if (container != null) + { + await container.RefreshAllMetadata(refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false); + } + else + { + await RefreshMetadataRecursive(refreshOptions, recursive, innerProgress, cancellationToken); + } + } progress.Report(100); } - /// <summary> - /// Refreshes the children. - /// </summary> - /// <param name="children">The children.</param> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="recursive">if set to <c>true</c> [recursive].</param> - /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param> - /// <returns>Task.</returns> - private async Task RefreshChildren(IList<Tuple<BaseItem, bool>> children, IProgress<double> progress, CancellationToken cancellationToken, bool? recursive, bool forceRefreshMetadata = false) + private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) { - var list = children; + var children = ActualChildren.ToList(); - var percentages = new Dictionary<Guid, double>(list.Count); + var percentages = new Dictionary<Guid, double>(children.Count); var tasks = new List<Task>(); - foreach (var tuple in list) + foreach (var child in children) { - if (tasks.Count > 10) + if (tasks.Count >= 8) { await Task.WhenAll(tasks).ConfigureAwait(false); + tasks.Clear(); } - tasks.Add(RefreshChild(tuple, progress, percentages, list.Count, cancellationToken, recursive, forceRefreshMetadata)); - } + cancellationToken.ThrowIfCancellationRequested(); + var innerProgress = new ActionableProgress<double>(); - cancellationToken.ThrowIfCancellationRequested(); + // Avoid implicitly captured closure + var currentChild = child; + innerProgress.RegisterAction(p => + { + lock (percentages) + { + percentages[currentChild.Id] = p / 100; + + var percent = percentages.Values.Sum(); + percent /= children.Count; + percent *= 100; + progress.Report(percent); + } + }); + + if (child.IsFolder) + { + await RefreshChildMetadata(child, refreshOptions, recursive, innerProgress, cancellationToken) + .ConfigureAwait(false); + } + else + { + tasks.Add(RefreshChildMetadata(child, refreshOptions, false, innerProgress, cancellationToken)); + } + } await Task.WhenAll(tasks).ConfigureAwait(false); + progress.Report(100); } - private async Task RefreshChild(Tuple<BaseItem, bool> currentTuple, IProgress<double> progress, Dictionary<Guid, double> percentages, int childCount, CancellationToken cancellationToken, bool? recursive, bool forceRefreshMetadata = false) + private async Task RefreshChildMetadata(BaseItem child, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + var container = child as IMetadataContainer; - var child = currentTuple.Item1; - try + if (container != null) { - //refresh it - await child.RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = currentTuple.Item2, - ReplaceAllMetadata = forceRefreshMetadata - - }, cancellationToken).ConfigureAwait(false); + await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false); } - catch (IOException ex) + else { - Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name); - } - - // Refresh children if a folder and the item changed or recursive is set to true - var refreshChildren = child.IsFolder && (currentTuple.Item2 || (recursive.HasValue && recursive.Value)); + await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); - if (refreshChildren) - { - // Don't refresh children if explicitly set to false - if (recursive.HasValue && recursive.Value == false) + if (recursive) { - refreshChildren = false; + var folder = child as Folder; + + if (folder != null) + { + await folder.RefreshMetadataRecursive(refreshOptions, true, progress, cancellationToken); + } } } + progress.Report(100); + } + + /// <summary> + /// Refreshes the children. + /// </summary> + /// <param name="children">The children.</param> + /// <param name="directoryService">The directory service.</param> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken) + { + var list = children; + var childCount = list.Count; + + var percentages = new Dictionary<Guid, double>(list.Count); - if (refreshChildren) + foreach (var item in list) { cancellationToken.ThrowIfCancellationRequested(); + var child = item; + var innerProgress = new ActionableProgress<double>(); innerProgress.RegisterAction(p => @@ -574,23 +617,12 @@ namespace MediaBrowser.Controller.Entities var percent = percentages.Values.Sum(); percent /= childCount; - progress.Report((90 * percent) + 10); + progress.Report((10 * percent) + 10); } }); - await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false); - } - else - { - lock (percentages) - { - percentages[child.Id] = 1; - - var percent = percentages.Values.Sum(); - percent /= childCount; - - progress.Report((90 * percent) + 10); - } + await child.ValidateChildrenWithCancellationSupport(innerProgress, cancellationToken, true, false, null, directoryService) + .ConfigureAwait(false); } } @@ -647,9 +679,9 @@ namespace MediaBrowser.Controller.Entities /// Get the children of this folder from the actual file system /// </summary> /// <returns>IEnumerable{BaseItem}.</returns> - protected virtual IEnumerable<BaseItem> GetNonCachedChildren() + protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) { - return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(), this); + return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(directoryService), this); } /// <summary> @@ -895,17 +927,21 @@ namespace MediaBrowser.Controller.Entities return item; } - protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) + protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { + var changesFound = false; + if (SupportsShortcutChildren && LocationType == LocationType.FileSystem) { if (RefreshLinkedChildren(fileSystemChildren)) { - options.ForceSave = true; + changesFound = true; } } - return base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken); + var baseHasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); + + return baseHasChanges || changesFound; } /// <summary> @@ -967,11 +1003,11 @@ namespace MediaBrowser.Controller.Entities /// <returns>Task.</returns> public override async Task ChangedExternally() { - await base.ChangedExternally().ConfigureAwait(false); - var progress = new Progress<double>(); await ValidateChildren(progress, CancellationToken.None).ConfigureAwait(false); + + await base.ChangedExternally().ConfigureAwait(false); } /// <summary> @@ -1016,50 +1052,17 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException(); } - try - { - var locationType = LocationType; - - if (locationType == LocationType.Remote && string.Equals(Path, path, StringComparison.OrdinalIgnoreCase)) - { - return this; - } - - if (locationType != LocationType.Virtual && PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) - { - return this; - } - } - catch (IOException ex) + if (string.Equals(Path, path, StringComparison.OrdinalIgnoreCase)) { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); + return this; } - return RecursiveChildren.Where(i => i.LocationType != LocationType.Virtual).FirstOrDefault(i => + if (PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) { - try - { - if (string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - if (i.LocationType != LocationType.Remote) - { - if (i.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - } + return this; + } - return false; - } - catch (IOException ex) - { - Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); - return false; - } - }); + return RecursiveChildren.FirstOrDefault(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase)); } public override bool IsPlayed(User user) |
