|
@@ -307,7 +307,17 @@ namespace MediaBrowser.Controller.Entities
|
|
/// <param name="recursive">if set to <c>true</c> [recursive].</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="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
|
|
/// <returns>Task.</returns>
|
|
/// <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, bool? recursive = null, bool forceRefreshMetadata = false)
|
|
|
|
+ {
|
|
|
|
+ return ValidateChildrenWithCancellationSupport(progress, cancellationToken, recursive ?? true, true,
|
|
|
|
+
|
|
|
|
+ new MetadataRefreshOptions
|
|
|
|
+ {
|
|
|
|
+ ReplaceAllMetadata = forceRefreshMetadata
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private async Task ValidateChildrenWithCancellationSupport(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions)
|
|
{
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
@@ -327,7 +337,7 @@ namespace MediaBrowser.Controller.Entities
|
|
|
|
|
|
var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken);
|
|
var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken);
|
|
|
|
|
|
- await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, forceRefreshMetadata).ConfigureAwait(false);
|
|
|
|
|
|
+ await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, refreshChildMetadata, refreshOptions).ConfigureAwait(false);
|
|
}
|
|
}
|
|
catch (OperationCanceledException ex)
|
|
catch (OperationCanceledException ex)
|
|
{
|
|
{
|
|
@@ -352,15 +362,15 @@ namespace MediaBrowser.Controller.Entities
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <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>
|
|
/// </summary>
|
|
/// <param name="progress">The progress.</param>
|
|
/// <param name="progress">The progress.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="recursive">if set to <c>true</c> [recursive].</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>
|
|
/// <returns>Task.</returns>
|
|
/// <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)
|
|
{
|
|
{
|
|
var locationType = LocationType;
|
|
var locationType = LocationType;
|
|
|
|
|
|
@@ -401,33 +411,23 @@ namespace MediaBrowser.Controller.Entities
|
|
|
|
|
|
if (currentChildren.TryGetValue(child.Id, out currentChild))
|
|
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)
|
|
{
|
|
{
|
|
- var currentChildLocationType = currentChild.LocationType;
|
|
|
|
- if (currentChildLocationType != LocationType.Remote &&
|
|
|
|
- currentChildLocationType != LocationType.Virtual)
|
|
|
|
- {
|
|
|
|
- currentChild.DateModified = child.DateModified;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- currentChild.IsInMixedFolder = child.IsInMixedFolder;
|
|
|
|
- validChildren.Add(currentChild);
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- validChildren.Add(currentChild);
|
|
|
|
|
|
+ currentChild.DateModified = child.DateModified;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ currentChild.IsInMixedFolder = child.IsInMixedFolder;
|
|
currentChild.IsOffline = false;
|
|
currentChild.IsOffline = false;
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
//brand new item - needs to be added
|
|
//brand new item - needs to be added
|
|
newItems.Add(child);
|
|
newItems.Add(child);
|
|
-
|
|
|
|
- validChildren.Add(child);
|
|
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ validChildren.Add(currentChild);
|
|
}
|
|
}
|
|
|
|
|
|
// If any items were added or removed....
|
|
// If any items were added or removed....
|
|
@@ -435,7 +435,6 @@ namespace MediaBrowser.Controller.Entities
|
|
{
|
|
{
|
|
// That's all the new and changed ones - now see if there are any that are missing
|
|
// That's all the new and changed ones - now see if there are any that are missing
|
|
var itemsRemoved = currentChildren.Values.Except(validChildren).ToList();
|
|
var itemsRemoved = currentChildren.Values.Except(validChildren).ToList();
|
|
-
|
|
|
|
var actualRemovals = new List<BaseItem>();
|
|
var actualRemovals = new List<BaseItem>();
|
|
|
|
|
|
foreach (var item in itemsRemoved)
|
|
foreach (var item in itemsRemoved)
|
|
@@ -450,7 +449,6 @@ namespace MediaBrowser.Controller.Entities
|
|
else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
|
|
else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
|
|
{
|
|
{
|
|
item.IsOffline = true;
|
|
item.IsOffline = true;
|
|
-
|
|
|
|
validChildren.Add(item);
|
|
validChildren.Add(item);
|
|
}
|
|
}
|
|
else
|
|
else
|
|
@@ -486,78 +484,135 @@ namespace MediaBrowser.Controller.Entities
|
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
- await RefreshChildren(validChildren, progress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
|
|
|
|
|
|
+ if (recursive)
|
|
|
|
+ {
|
|
|
|
+ await ValidateSubFolders(validChildren.OfType<Folder>().ToList(), 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);
|
|
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<BaseItem> 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>();
|
|
var tasks = new List<Task>();
|
|
|
|
|
|
- foreach (var tuple in list)
|
|
|
|
|
|
+ foreach (var child in children)
|
|
{
|
|
{
|
|
- if (tasks.Count > 10)
|
|
|
|
|
|
+ if (tasks.Count > 3)
|
|
{
|
|
{
|
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
|
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>();
|
|
|
|
+
|
|
|
|
+ // Avoid implicitly captured closure
|
|
|
|
+ var currentChild = child;
|
|
|
|
+ innerProgress.RegisterAction(p =>
|
|
|
|
+ {
|
|
|
|
+ lock (percentages)
|
|
|
|
+ {
|
|
|
|
+ percentages[currentChild.Id] = p / 100;
|
|
|
|
|
|
- cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
+ 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, recursive, innerProgress, cancellationToken));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
|
|
|
+ progress.Report(100);
|
|
}
|
|
}
|
|
|
|
|
|
- private async Task RefreshChild(BaseItem item, 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 = item;
|
|
|
|
- try
|
|
|
|
|
|
+ if (container != null)
|
|
{
|
|
{
|
|
- //refresh it
|
|
|
|
- await child.RefreshMetadata(new MetadataRefreshOptions
|
|
|
|
- {
|
|
|
|
- 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);
|
|
|
|
|
|
+ await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
|
|
|
|
+
|
|
|
|
+ if (recursive)
|
|
|
|
+ {
|
|
|
|
+ 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="progress">The progress.</param>
|
|
|
|
+ /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
|
+ /// <returns>Task.</returns>
|
|
|
|
+ private async Task ValidateSubFolders(IList<Folder> children, IProgress<double> progress, CancellationToken cancellationToken)
|
|
|
|
+ {
|
|
|
|
+ var list = children;
|
|
|
|
+ var childCount = list.Count;
|
|
|
|
|
|
- // Refresh children if a folder and the item changed or recursive is set to true
|
|
|
|
- var refreshChildren = child.IsFolder;
|
|
|
|
|
|
+ var percentages = new Dictionary<Guid, double>(list.Count);
|
|
|
|
+
|
|
|
|
+ var tasks = new List<Task>();
|
|
|
|
|
|
- if (refreshChildren)
|
|
|
|
|
|
+ foreach (var item in list)
|
|
{
|
|
{
|
|
- // Don't refresh children if explicitly set to false
|
|
|
|
- if (recursive.HasValue && recursive.Value == false)
|
|
|
|
|
|
+ if (tasks.Count > 10)
|
|
{
|
|
{
|
|
- refreshChildren = false;
|
|
|
|
|
|
+ await Task.WhenAll(tasks).ConfigureAwait(false);
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- if (refreshChildren)
|
|
|
|
- {
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
|
|
+ var child = item;
|
|
|
|
+
|
|
var innerProgress = new ActionableProgress<double>();
|
|
var innerProgress = new ActionableProgress<double>();
|
|
|
|
|
|
innerProgress.RegisterAction(p =>
|
|
innerProgress.RegisterAction(p =>
|
|
@@ -569,24 +624,16 @@ namespace MediaBrowser.Controller.Entities
|
|
var percent = percentages.Values.Sum();
|
|
var percent = percentages.Values.Sum();
|
|
percent /= childCount;
|
|
percent /= childCount;
|
|
|
|
|
|
- progress.Report((90 * percent) + 10);
|
|
|
|
|
|
+ progress.Report((10 * percent) + 10);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
- await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
|
|
|
|
|
|
+ tasks.Add(child.ValidateChildrenWithCancellationSupport(innerProgress, cancellationToken, true, false, null));
|
|
}
|
|
}
|
|
- else
|
|
|
|
- {
|
|
|
|
- lock (percentages)
|
|
|
|
- {
|
|
|
|
- percentages[child.Id] = 1;
|
|
|
|
|
|
|
|
- var percent = percentages.Values.Sum();
|
|
|
|
- percent /= childCount;
|
|
|
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
- progress.Report((90 * percent) + 10);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ await Task.WhenAll(tasks).ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -962,11 +1009,11 @@ namespace MediaBrowser.Controller.Entities
|
|
/// <returns>Task.</returns>
|
|
/// <returns>Task.</returns>
|
|
public override async Task ChangedExternally()
|
|
public override async Task ChangedExternally()
|
|
{
|
|
{
|
|
- await base.ChangedExternally().ConfigureAwait(false);
|
|
|
|
-
|
|
|
|
var progress = new Progress<double>();
|
|
var progress = new Progress<double>();
|
|
|
|
|
|
await ValidateChildren(progress, CancellationToken.None).ConfigureAwait(false);
|
|
await ValidateChildren(progress, CancellationToken.None).ConfigureAwait(false);
|
|
|
|
+
|
|
|
|
+ await base.ChangedExternally().ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|