support individual library refreshing

This commit is contained in:
Luke Pulverenti
2017-06-23 12:04:45 -04:00
parent 6ff89eab78
commit 1e5c3db9eb
44 changed files with 523 additions and 261 deletions

View File

@@ -6,6 +6,7 @@ using System.Linq;
using MediaBrowser.Model.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Progress;
namespace MediaBrowser.Controller.Channels
{
@@ -51,7 +52,7 @@ namespace MediaBrowser.Controller.Channels
SortBy = query.SortBy,
SortOrder = query.SortOrder
}, new Progress<double>(), CancellationToken.None).Result;
}, new SimpleProgress<double>(), CancellationToken.None).Result;
}
catch
{

View File

@@ -10,7 +10,8 @@ namespace MediaBrowser.Controller.Dto
{
private static readonly List<ItemFields> DefaultExcludedFields = new List<ItemFields>
{
ItemFields.SeasonUserData
ItemFields.SeasonUserData,
ItemFields.RefreshState
};
public List<ItemFields> Fields { get; set; }

View File

@@ -235,8 +235,6 @@ namespace MediaBrowser.Controller.Entities.Audio
{
await RefreshArtists(refreshOptions, cancellationToken).ConfigureAwait(false);
}
progress.Report(100);
}
private async Task RefreshArtists(MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)

View File

@@ -250,8 +250,6 @@ namespace MediaBrowser.Controller.Entities.Audio
percent /= totalItems;
progress.Report(percent * 100);
}
progress.Report(100);
}
public ArtistInfo GetLookupInfo()

View File

@@ -1058,6 +1058,16 @@ namespace MediaBrowser.Controller.Entities
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)), cancellationToken);
}
protected virtual void TriggerOnRefreshStart()
{
}
protected virtual void TriggerOnRefreshComplete()
{
}
/// <summary>
/// Overrides the base implementation to refresh metadata for local trailers
/// </summary>
@@ -1066,6 +1076,8 @@ namespace MediaBrowser.Controller.Entities
/// <returns>true if a provider reports we changed</returns>
public async Task<ItemUpdateType> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
{
TriggerOnRefreshStart();
var locationType = LocationType;
var requiresSave = false;
@@ -1091,14 +1103,21 @@ namespace MediaBrowser.Controller.Entities
}
}
var refreshOptions = requiresSave
? new MetadataRefreshOptions(options)
{
ForceSave = true
}
: options;
try
{
var refreshOptions = requiresSave
? new MetadataRefreshOptions(options)
{
ForceSave = true
}
: options;
return await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
return await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
}
finally
{
TriggerOnRefreshComplete();
}
}
[IgnoreDataMember]
@@ -2421,5 +2440,10 @@ namespace MediaBrowser.Controller.Entities
{
return new List<ExternalUrl>();
}
public virtual double? GetRefreshProgress()
{
return null;
}
}
}

View File

@@ -10,6 +10,7 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
@@ -199,6 +200,30 @@ namespace MediaBrowser.Controller.Entities
return changed;
}
public override double? GetRefreshProgress()
{
var folders = GetPhysicalFolders(true).ToList();
double totalProgresses = 0;
var foldersWithProgress = 0;
foreach (var folder in folders)
{
var progress = ProviderManager.GetRefreshProgress(folder.Id);
if (progress.HasValue)
{
totalProgresses += progress.Value;
foldersWithProgress++;
}
}
if (foldersWithProgress == 0)
{
return null;
}
return (totalProgresses / foldersWithProgress);
}
protected override bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
{
return RefreshLinkedChildrenInternal(true);
@@ -321,6 +346,11 @@ namespace MediaBrowser.Controller.Entities
return GetPhysicalFolders(true).SelectMany(c => c.Children);
}
public IEnumerable<Folder> GetPhysicalFolders()
{
return GetPhysicalFolders(true);
}
private IEnumerable<Folder> GetPhysicalFolders(bool enableCache)
{
if (enableCache)

View File

@@ -271,6 +271,11 @@ namespace MediaBrowser.Controller.Entities
return GetCachedChildren();
}
public override double? GetRefreshProgress()
{
return ProviderManager.GetRefreshProgress(Id);
}
public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken)
{
return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)));
@@ -318,6 +323,14 @@ namespace MediaBrowser.Controller.Entities
return current.IsValidFromResolver(newItem);
}
protected override void TriggerOnRefreshStart()
{
}
protected override void TriggerOnRefreshComplete()
{
}
/// <summary>
/// Validates the children internal.
/// </summary>
@@ -328,7 +341,27 @@ namespace MediaBrowser.Controller.Entities
/// <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)
protected virtual async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
if (recursive)
{
ProviderManager.OnRefreshStart(this);
}
try
{
await ValidateChildrenInternal2(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService).ConfigureAwait(false);
}
finally
{
if (recursive)
{
ProviderManager.OnRefreshComplete(this);
}
}
}
private async Task ValidateChildrenInternal2(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
var locationType = LocationType;
@@ -360,6 +393,11 @@ namespace MediaBrowser.Controller.Entities
progress.Report(5);
if (recursive)
{
ProviderManager.OnRefreshProgress(this, 5);
}
//build a dictionary of the current children we have now by Id so we can compare quickly and easily
var currentChildren = GetActualChildrenDictionary();
@@ -424,76 +462,99 @@ namespace MediaBrowser.Controller.Entities
await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
}
}
else
{
if (recursive || refreshChildMetadata)
{
// used below
validChildren = Children.ToList();
}
}
progress.Report(10);
if (recursive)
{
ProviderManager.OnRefreshProgress(this, 10);
}
cancellationToken.ThrowIfCancellationRequested();
if (recursive)
{
await ValidateSubFolders(Children.ToList().OfType<Folder>().ToList(), directoryService, progress, cancellationToken).ConfigureAwait(false);
}
using (var innerProgress = new ActionableProgress<double>())
{
var folder = this;
innerProgress.RegisterAction(p =>
{
double newPct = .70 * p + 10;
progress.Report(newPct);
ProviderManager.OnRefreshProgress(folder, newPct);
});
progress.Report(20);
await ValidateSubFolders(validChildren.OfType<Folder>().ToList(), directoryService, innerProgress, cancellationToken).ConfigureAwait(false);
}
}
if (refreshChildMetadata)
{
progress.Report(80);
if (recursive)
{
ProviderManager.OnRefreshProgress(this, 80);
}
var container = this as IMetadataContainer;
var innerProgress = new ActionableProgress<double>();
innerProgress.RegisterAction(p => progress.Report(.80 * p + 20));
if (container != null)
using (var innerProgress = new ActionableProgress<double>())
{
await container.RefreshAllMetadata(refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false);
}
else
{
await RefreshMetadataRecursive(refreshOptions, recursive, innerProgress, cancellationToken);
var folder = this;
innerProgress.RegisterAction(p =>
{
double newPct = .20 * p + 80;
progress.Report(newPct);
if (recursive)
{
ProviderManager.OnRefreshProgress(folder, newPct);
}
});
if (container != null)
{
await container.RefreshAllMetadata(refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false);
}
else
{
await RefreshMetadataRecursive(validChildren, refreshOptions, recursive, innerProgress, cancellationToken);
}
}
}
progress.Report(100);
}
private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
private async Task RefreshMetadataRecursive(List<BaseItem> children, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
{
var children = Children.ToList();
var percentages = new Dictionary<Guid, double>(children.Count);
var numComplete = 0;
var count = children.Count;
double currentPercent = 0;
foreach (var child in children)
{
cancellationToken.ThrowIfCancellationRequested();
if (child.IsFolder)
using (var innerProgress = new ActionableProgress<double>())
{
var innerProgress = new ActionableProgress<double>();
// Avoid implicitly captured closure
var currentChild = child;
var currentInnerPercent = currentPercent;
innerProgress.RegisterAction(p =>
{
lock (percentages)
{
percentages[currentChild.Id] = p / 100;
var innerPercent = percentages.Values.Sum();
innerPercent /= count;
innerPercent *= 100;
progress.Report(innerPercent);
}
double innerPercent = currentInnerPercent;
innerPercent += p / (count);
progress.Report(innerPercent);
});
await RefreshChildMetadata(child, refreshOptions, recursive, innerProgress, cancellationToken)
.ConfigureAwait(false);
}
else
{
await RefreshChildMetadata(child, refreshOptions, false, new Progress<double>(), cancellationToken)
await RefreshChildMetadata(child, refreshOptions, recursive && child.IsFolder, innerProgress, cancellationToken)
.ConfigureAwait(false);
}
@@ -501,11 +562,10 @@ namespace MediaBrowser.Controller.Entities
double percent = numComplete;
percent /= count;
percent *= 100;
currentPercent = percent;
progress.Report(percent);
}
progress.Report(100);
}
private async Task RefreshChildMetadata(BaseItem child, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
@@ -526,11 +586,10 @@ namespace MediaBrowser.Controller.Entities
if (folder != null)
{
await folder.RefreshMetadataRecursive(refreshOptions, true, progress, cancellationToken);
await folder.RefreshMetadataRecursive(folder.Children.ToList(), refreshOptions, true, progress, cancellationToken);
}
}
}
progress.Report(100);
}
/// <summary>
@@ -543,47 +602,40 @@ namespace MediaBrowser.Controller.Entities
/// <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 numComplete = 0;
var count = children.Count;
double currentPercent = 0;
var percentages = new Dictionary<Guid, double>(list.Count);
foreach (var item in list)
foreach (var child in children)
{
cancellationToken.ThrowIfCancellationRequested();
var child = item;
var innerProgress = new ActionableProgress<double>();
innerProgress.RegisterAction(p =>
using (var innerProgress = new ActionableProgress<double>())
{
lock (percentages)
// Avoid implicitly captured closure
var currentInnerPercent = currentPercent;
innerProgress.RegisterAction(p =>
{
percentages[child.Id] = p / 100;
double innerPercent = currentInnerPercent;
innerPercent += p / (count);
progress.Report(innerPercent);
});
var percent = percentages.Values.Sum();
percent /= childCount;
await child.ValidateChildrenInternal(innerProgress, cancellationToken, true, false, null, directoryService)
.ConfigureAwait(false);
}
progress.Report(10 * percent + 10);
}
});
numComplete++;
double percent = numComplete;
percent /= count;
percent *= 100;
currentPercent = percent;
await child.ValidateChildrenInternal(innerProgress, cancellationToken, true, false, null, directoryService)
.ConfigureAwait(false);
progress.Report(percent);
}
}
/// <summary>
/// Determines whether the specified path is offline.
/// </summary>
/// <param name="path">The path.</param>
/// <returns><c>true</c> if the specified path is offline; otherwise, <c>false</c>.</returns>
public static bool IsPathOffline(string path)
{
return IsPathOffline(path, LibraryManager.GetVirtualFolders().SelectMany(i => i.Locations).ToList());
}
public static bool IsPathOffline(string path, List<string> allLibraryPaths)
{
if (FileSystem.FileExists(path))
@@ -926,7 +978,7 @@ namespace MediaBrowser.Controller.Entities
SortBy = query.SortBy,
SortOrder = query.SortOrder
}, new Progress<double>(), CancellationToken.None).Result;
}, new SimpleProgress<double>(), CancellationToken.None).Result;
}
catch
{

View File

@@ -414,8 +414,6 @@ namespace MediaBrowser.Controller.Entities.TV
refreshOptions = new MetadataRefreshOptions(refreshOptions);
refreshOptions.IsPostRecursiveRefresh = true;
await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
progress.Report(100);
}
public IEnumerable<Episode> GetSeasonEpisodes(Season parentSeason, User user, DtoOptions options)

View File

@@ -132,7 +132,9 @@ namespace MediaBrowser.Controller.Library
/// Gets the default view.
/// </summary>
/// <returns>IEnumerable{VirtualFolderInfo}.</returns>
IEnumerable<VirtualFolderInfo> GetVirtualFolders();
List<VirtualFolderInfo> GetVirtualFolders();
List<VirtualFolderInfo> GetVirtualFolders(bool includeRefreshState);
/// <summary>
/// Gets the item by id.

View File

@@ -320,7 +320,6 @@
<Compile Include="Plugins\IPluginConfigurationPage.cs" />
<Compile Include="Plugins\IServerEntryPoint.cs" />
<Compile Include="Providers\IImageEnhancer.cs" />
<Compile Include="Providers\ProviderRefreshStatus.cs" />
<Compile Include="Resolvers\IResolverIgnoreRule.cs" />
<Compile Include="Resolvers\ResolverPriority.cs" />
<Compile Include="Library\TVUtils.cs" />

View File

@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Events;
namespace MediaBrowser.Controller.Providers
{
@@ -30,7 +31,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task RefreshFullItem(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken);
/// <summary>
/// Refreshes the metadata.
/// </summary>
@@ -68,7 +69,7 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <returns>Task.</returns>
Task SaveImage(IHasImages item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken);
/// <summary>
/// Adds the metadata providers.
/// </summary>
@@ -128,7 +129,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="savers">The savers.</param>
/// <returns>Task.</returns>
Task SaveMetadata(IHasMetadata item, ItemUpdateType updateType, IEnumerable<string> savers);
/// <summary>
/// Gets the metadata options.
/// </summary>
@@ -158,6 +159,18 @@ namespace MediaBrowser.Controller.Providers
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken);
Dictionary<Guid, Guid> GetRefreshQueue();
void OnRefreshStart(BaseItem item);
void OnRefreshProgress(BaseItem item, double progress);
void OnRefreshComplete(BaseItem item);
double? GetRefreshProgress(Guid id);
event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
}
public enum RefreshPriority

View File

@@ -1,22 +0,0 @@

namespace MediaBrowser.Controller.Providers
{
/// <summary>
/// Enum ProviderRefreshStatus
/// </summary>
public enum ProviderRefreshStatus
{
/// <summary>
/// The success
/// </summary>
Success = 0,
/// <summary>
/// The completed with errors
/// </summary>
CompletedWithErrors = 1,
/// <summary>
/// The failure
/// </summary>
Failure = 2
}
}