aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/Entities/Folder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller/Entities/Folder.cs')
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs283
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)