/// <summary> /// 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="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, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { var locationType = LocationType; cancellationToken.ThrowIfCancellationRequested(); var validChildren = new List <BaseItem>(); if (locationType != LocationType.Remote && locationType != LocationType.Virtual) { IEnumerable <BaseItem> nonCachedChildren; try { nonCachedChildren = GetNonCachedChildren(directoryService); } catch (IOException ex) { nonCachedChildren = new BaseItem[] { }; Logger.ErrorException("Error getting file system entries for {0}", ex, Path); } if (nonCachedChildren == null) { return; //nothing to validate } progress.Report(5); //build a dictionary of the current children we have now by Id so we can compare quickly and easily var currentChildren = GetActualChildrenDictionary(); //create a list for our validated children var newItems = new List <BaseItem>(); cancellationToken.ThrowIfCancellationRequested(); foreach (var child in nonCachedChildren) { BaseItem currentChild; if (currentChildren.TryGetValue(child.Id, out currentChild)) { if (IsValidFromResolver(currentChild, child)) { var currentChildLocationType = currentChild.LocationType; if (currentChildLocationType != LocationType.Remote && currentChildLocationType != LocationType.Virtual) { currentChild.DateModified = child.DateModified; } currentChild.IsOffline = false; validChildren.Add(currentChild); } else { newItems.Add(child); validChildren.Add(child); } } else { // Brand new item - needs to be added newItems.Add(child); validChildren.Add(child); } } // If any items were added or removed.... if (newItems.Count > 0 || currentChildren.Count != validChildren.Count) { // 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 actualRemovals = new List <BaseItem>(); foreach (var item in itemsRemoved) { if (item.LocationType == LocationType.Virtual || item.LocationType == LocationType.Remote) { // Don't remove these because there's no way to accurately validate them. validChildren.Add(item); } else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path)) { item.IsOffline = true; validChildren.Add(item); } else { item.IsOffline = false; actualRemovals.Add(item); } } if (actualRemovals.Count > 0) { RemoveChildrenInternal(actualRemovals); foreach (var item in actualRemovals) { LibraryManager.ReportItemRemoved(item); } } await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false); AddChildrenInternal(newItems); await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false); } } progress.Report(10); cancellationToken.ThrowIfCancellationRequested(); 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> /// 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*** /// </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> /// <returns>Task.</returns> protected async virtual Task ValidateChildrenInternal(IProgress <double> progress, CancellationToken cancellationToken, bool?recursive = null, bool forceRefreshMetadata = false) { var locationType = LocationType; // Nothing to do here if (locationType == LocationType.Remote || locationType == LocationType.Virtual) { return; } cancellationToken.ThrowIfCancellationRequested(); IEnumerable <BaseItem> nonCachedChildren; try { nonCachedChildren = GetNonCachedChildren(); } catch (IOException ex) { nonCachedChildren = new BaseItem[] { }; Logger.ErrorException("Error getting file system entries for {0}", ex, Path); } if (nonCachedChildren == null) { return; //nothing to validate } progress.Report(5); //build a dictionary of the current children we have now by Id so we can compare quickly and easily var currentChildren = ActualChildren; //create a list for our validated children var validChildren = new ConcurrentBag <Tuple <BaseItem, bool> >(); var newItems = new ConcurrentBag <BaseItem>(); cancellationToken.ThrowIfCancellationRequested(); var options = new ParallelOptions { MaxDegreeOfParallelism = 20 }; Parallel.ForEach(nonCachedChildren, options, child => { BaseItem currentChild; if (currentChildren.TryGetValue(child.Id, out currentChild)) { currentChild.ResolveArgs = child.ResolveArgs; //existing item - check if it has changed if (currentChild.HasChanged(child)) { EntityResolutionHelper.EnsureDates(currentChild, child.ResolveArgs, false); validChildren.Add(new Tuple <BaseItem, bool>(currentChild, true)); } else { validChildren.Add(new Tuple <BaseItem, bool>(currentChild, false)); } currentChild.IsOffline = false; } else { //brand new item - needs to be added newItems.Add(child); validChildren.Add(new Tuple <BaseItem, bool>(child, true)); } }); // If any items were added or removed.... if (!newItems.IsEmpty || 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(); foreach (var item in itemsRemoved) { if (IsRootPathAvailable(item.Path)) { item.IsOffline = false; BaseItem removed; if (!_children.TryRemove(item.Id, out removed)) { Logger.Error("Failed to remove {0}", item.Name); } else { LibraryManager.ReportItemRemoved(item); } } else { item.IsOffline = true; validChildren.Add(new Tuple <BaseItem, bool>(item, false)); } } await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false); foreach (var item in newItems) { if (!_children.TryAdd(item.Id, item)) { Logger.Error("Failed to add {0}", item.Name); } else { Logger.Debug("** " + item.Name + " Added to library."); } } await ItemRepository.SaveChildren(Id, _children.Values.ToList().Select(i => i.Id), cancellationToken).ConfigureAwait(false); //force the indexes to rebuild next time IndexCache.Clear(); } progress.Report(10); cancellationToken.ThrowIfCancellationRequested(); await RefreshChildren(validChildren, progress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false); progress.Report(100); }