diff options
16 files changed, 195 insertions, 93 deletions
diff --git a/MediaBrowser.Controller/Dto/DtoBuilder.cs b/MediaBrowser.Controller/Dto/DtoBuilder.cs index 84f9f8827..6e33f52e6 100644 --- a/MediaBrowser.Controller/Dto/DtoBuilder.cs +++ b/MediaBrowser.Controller/Dto/DtoBuilder.cs @@ -665,22 +665,6 @@ namespace MediaBrowser.Controller.Dto } /// <summary> - /// Gets the library update info. - /// </summary> - /// <param name="changeEvent">The <see cref="ChildrenChangedEventArgs" /> instance containing the event data.</param> - /// <returns>LibraryUpdateInfo.</returns> - public static LibraryUpdateInfo GetLibraryUpdateInfo(ChildrenChangedEventArgs changeEvent) - { - return new LibraryUpdateInfo - { - Folder = GetBaseItemInfo(changeEvent.Folder), - ItemsAdded = changeEvent.ItemsAdded.Select(GetBaseItemInfo), - ItemsRemoved = changeEvent.ItemsRemoved.Select(i => i.Id), - ItemsUpdated = changeEvent.ItemsUpdated.Select(i => i.Id) - }; - } - - /// <summary> /// Converts a UserItemData to a DTOUserItemData /// </summary> /// <param name="data">The data.</param> diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index b7508a641..f8db07fe3 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -659,7 +659,7 @@ namespace MediaBrowser.Controller.Entities foreach (var tuple in list) { - if (tasks.Count > 50) + if (tasks.Count > 5) { await Task.WhenAll(tasks).ConfigureAwait(false); } diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index e0091cd80..d5df769a9 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -49,8 +49,17 @@ namespace MediaBrowser.Controller.Providers throw new ArgumentNullException(); } + var settings = new XmlReaderSettings + { + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true, + IgnoreWhitespace = true, + ValidationType = ValidationType.None + }; + // Use XmlReader for best performance - using (var reader = XmlReader.Create(metadataFile)) + using (var reader = XmlReader.Create(metadataFile, settings)) { reader.MoveToContent(); @@ -93,7 +102,7 @@ namespace MediaBrowser.Controller.Providers { var type = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(type) && !type.Equals("none",StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(type) && !type.Equals("none", StringComparison.OrdinalIgnoreCase)) { item.DisplayMediaType = type; } diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs index 3f71e398a..b69ece418 100644 --- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -31,6 +31,8 @@ namespace MediaBrowser.Controller.Providers /// </summary> protected readonly Guid Id; + protected static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(5, 5); + /// <summary> /// Supportses the specified item. /// </summary> diff --git a/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs b/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs index d2c2d5b15..99744390a 100644 --- a/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs +++ b/MediaBrowser.Controller/Providers/FolderProviderFromXml.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Providers /// <returns>Task{System.Boolean}.</returns> public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - return Task.Run(() => Fetch(item, cancellationToken)); + return Fetch(item, cancellationToken); } /// <summary> @@ -67,7 +67,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool Fetch(BaseItem item, CancellationToken cancellationToken) + private async Task<bool> Fetch(BaseItem item, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -76,7 +76,18 @@ namespace MediaBrowser.Controller.Providers if (metadataFile.HasValue) { var path = metadataFile.Value.Path; - new BaseItemXmlParser<Folder>(Logger).Fetch((Folder)item, path, cancellationToken); + + await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + new BaseItemXmlParser<Folder>(Logger).Fetch((Folder)item, path, cancellationToken); + } + finally + { + XmlParsingResourcePool.Release(); + } + SetLastRefreshed(item, DateTime.UtcNow); return true; } diff --git a/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs b/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs index 279533636..1ed003e87 100644 --- a/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs +++ b/MediaBrowser.Controller/Providers/Movies/MovieProviderFromXml.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// <returns>Task{System.Boolean}.</returns> public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - return Task.Run(() => Fetch(item, cancellationToken)); + return Fetch(item, cancellationToken); } /// <summary> @@ -67,7 +67,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool Fetch(BaseItem item, CancellationToken cancellationToken) + private async Task<bool> Fetch(BaseItem item, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -77,14 +77,25 @@ namespace MediaBrowser.Controller.Providers.Movies { var path = metadataFile.Value.Path; var boxset = item as BoxSet; - if (boxset != null) + + await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try { - new BaseItemXmlParser<BoxSet>(Logger).Fetch(boxset, path, cancellationToken); + if (boxset != null) + { + new BaseItemXmlParser<BoxSet>(Logger).Fetch(boxset, path, cancellationToken); + } + else + { + new BaseItemXmlParser<Movie>(Logger).Fetch((Movie)item, path, cancellationToken); + } } - else + finally { - new BaseItemXmlParser<Movie>(Logger).Fetch((Movie)item, path, cancellationToken); + XmlParsingResourcePool.Release(); } + SetLastRefreshed(item, DateTime.UtcNow); return true; } diff --git a/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs b/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs index 483d1ddce..e6fb2eb4f 100644 --- a/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs +++ b/MediaBrowser.Controller/Providers/TV/EpisodeProviderFromXml.cs @@ -15,7 +15,8 @@ namespace MediaBrowser.Controller.Providers.TV /// </summary> public class EpisodeProviderFromXml : BaseMetadataProvider { - public EpisodeProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) + public EpisodeProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager) + : base(logManager, configurationManager) { } @@ -55,7 +56,7 @@ namespace MediaBrowser.Controller.Providers.TV /// <returns>Task{System.Boolean}.</returns> public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - return Task.Run(() => Fetch(item, cancellationToken)); + return Fetch(item, cancellationToken); } /// <summary> @@ -84,42 +85,31 @@ namespace MediaBrowser.Controller.Providers.TV /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool Fetch(BaseItem item, CancellationToken cancellationToken) + private async Task<bool> Fetch(BaseItem item, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - + var metadataFile = Path.Combine(item.MetaLocation, Path.ChangeExtension(Path.GetFileName(item.Path), ".xml")); - var episode = (Episode)item; + var file = item.ResolveArgs.Parent.ResolveArgs.GetMetaFileByPath(metadataFile); - if (!FetchMetadata(episode, item.ResolveArgs.Parent, metadataFile, cancellationToken)) + if (!file.HasValue) { - // Don't set last refreshed if we didn't do anything return false; } - SetLastRefreshed(item, DateTime.UtcNow); - return true; - } - - /// <summary> - /// Fetches the metadata. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="parent">The parent.</param> - /// <param name="metadataFile">The metadata file.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool FetchMetadata(Episode item, Folder parent, string metadataFile, CancellationToken cancellationToken) - { - var file = parent.ResolveArgs.GetMetaFileByPath(metadataFile); + await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - if (!file.HasValue) + try { - return false; + new EpisodeXmlParser(Logger).Fetch((Episode)item, metadataFile, cancellationToken); + } + finally + { + XmlParsingResourcePool.Release(); } - new EpisodeXmlParser(Logger).Fetch(item, metadataFile, cancellationToken); + SetLastRefreshed(item, DateTime.UtcNow); return true; } } diff --git a/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs b/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs index 8e42e9d55..556ef24c9 100644 --- a/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs +++ b/MediaBrowser.Controller/Providers/TV/SeriesProviderFromXml.cs @@ -59,7 +59,7 @@ namespace MediaBrowser.Controller.Providers.TV /// <returns>Task{System.Boolean}.</returns> public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - return Task.Run(() => Fetch(item, cancellationToken)); + return Fetch(item, cancellationToken); } /// <summary> @@ -68,7 +68,7 @@ namespace MediaBrowser.Controller.Providers.TV /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool Fetch(BaseItem item, CancellationToken cancellationToken) + private async Task<bool> Fetch(BaseItem item, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -78,7 +78,17 @@ namespace MediaBrowser.Controller.Providers.TV { var path = metadataFile.Value.Path; - new SeriesXmlParser(Logger).Fetch((Series)item, path, cancellationToken); + await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + new SeriesXmlParser(Logger).Fetch((Series)item, path, cancellationToken); + } + finally + { + XmlParsingResourcePool.Release(); + } + SetLastRefreshed(item, DateTime.UtcNow); return true; diff --git a/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs b/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs index 1ad117009..e98b7ae2c 100644 --- a/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs +++ b/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs @@ -9,27 +9,35 @@ namespace MediaBrowser.Model.Entities public class LibraryUpdateInfo { /// <summary> - /// Gets or sets the folder. + /// Gets or sets the folders. /// </summary> - /// <value>The folder.</value> - public BaseItemInfo Folder { get; set; } + /// <value>The folders.</value> + public List<Guid> Folders { get; set; } /// <summary> /// Gets or sets the items added. /// </summary> /// <value>The items added.</value> - public IEnumerable<BaseItemInfo> ItemsAdded { get; set; } + public List<Guid> ItemsAdded { get; set; } /// <summary> /// Gets or sets the items removed. /// </summary> /// <value>The items removed.</value> - public IEnumerable<Guid> ItemsRemoved { get; set; } + public List<Guid> ItemsRemoved { get; set; } /// <summary> /// Gets or sets the items updated. /// </summary> /// <value>The items updated.</value> - public IEnumerable<Guid> ItemsUpdated { get; set; } + public List<Guid> ItemsUpdated { get; set; } + + public LibraryUpdateInfo() + { + Folders = new List<Guid>(); + ItemsAdded = new List<Guid>(); + ItemsRemoved = new List<Guid>(); + ItemsUpdated = new List<Guid>(); + } } } diff --git a/MediaBrowser.Model/Weather/WeatherInfo.cs b/MediaBrowser.Model/Weather/WeatherInfo.cs index b423380f5..57a9432f6 100644 --- a/MediaBrowser.Model/Weather/WeatherInfo.cs +++ b/MediaBrowser.Model/Weather/WeatherInfo.cs @@ -21,5 +21,13 @@ namespace MediaBrowser.Model.Weather /// <value>The forecasts.</value> [ProtoMember(2)] public WeatherForecast[] Forecasts { get; set; } + + /// <summary> + /// Initializes a new instance of the <see cref="WeatherInfo"/> class. + /// </summary> + public WeatherInfo() + { + Forecasts = new WeatherForecast[] {}; + } } } diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index 08e2eb774..709c21f50 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs @@ -55,7 +55,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// <summary> /// The audio image resource pool /// </summary> - private readonly SemaphoreSlim _audioImageResourcePool = new SemaphoreSlim(2, 2); + private readonly SemaphoreSlim _audioImageResourcePool = new SemaphoreSlim(1, 1); /// <summary> /// The _subtitle extraction resource pool diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/AudioImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/AudioImagesTask.cs index db809a47b..66ca9db9a 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/AudioImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/AudioImagesTask.cs @@ -147,6 +147,8 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks { // Image is already in the cache item.PrimaryImagePath = path; + + await _libraryManager.SaveItem(item, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index d46d4ec8a..7f158f1f2 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -60,40 +61,27 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) { - var videos = _libraryManager.RootFolder.RecursiveChildren.OfType<Video>().Where(v => v.Chapters != null).ToList(); + var videos = _libraryManager.RootFolder.RecursiveChildren + .OfType<Video>() + .Where(v => v.Chapters != null && v.Chapters.Count != 0) + .ToList(); var numComplete = 0; - var tasks = videos.Select(v => Task.Run(async () => + foreach (var video in videos) { - try - { - await _kernel.FFMpegManager.PopulateChapterImages(v, cancellationToken, true, true); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error creating chapter images for {0}", ex, v.Name); - } - finally - { - lock (progress) - { - numComplete++; - double percent = numComplete; - percent /= videos.Count; + cancellationToken.ThrowIfCancellationRequested(); + + await _kernel.FFMpegManager.PopulateChapterImages(video, cancellationToken, true, true); - progress.Report(100 * percent); - } - } - })); + numComplete++; + double percent = numComplete; + percent /= videos.Count; - return Task.WhenAll(tasks); + progress.Report(100 * percent); + } } /// <summary> diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs index a82c22fe9..a995f1a47 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs @@ -176,6 +176,8 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks { // Image is already in the cache item.PrimaryImagePath = path; + + await _libraryManager.SaveItem(item, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs b/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs index 19c42e8d8..a8eb8f4c0 100644 --- a/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs +++ b/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Common.Events; +using System.Linq; +using System.Threading; +using MediaBrowser.Common.Events; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.ScheduledTasks; @@ -8,6 +10,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Updates; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Updates; @@ -49,8 +52,33 @@ namespace MediaBrowser.ServerApplication.EntryPoints /// </summary> private readonly IServerApplicationHost _appHost; + /// <summary> + /// The _task manager + /// </summary> private readonly ITaskManager _taskManager; - + + /// <summary> + /// The _library changed sync lock + /// </summary> + private readonly object _libraryChangedSyncLock = new object(); + + /// <summary> + /// Gets or sets the library update info. + /// </summary> + /// <value>The library update info.</value> + private LibraryUpdateInfo LibraryUpdateInfo { get; set; } + + /// <summary> + /// Gets or sets the library update timer. + /// </summary> + /// <value>The library update timer.</value> + private Timer LibraryUpdateTimer { get; set; } + + /// <summary> + /// The library update duration + /// </summary> + private const int LibraryUpdateDuration = 60000; + /// <summary> /// Initializes a new instance of the <see cref="WebSocketEvents" /> class. /// </summary> @@ -145,7 +173,47 @@ namespace MediaBrowser.ServerApplication.EntryPoints /// <param name="e">The <see cref="ChildrenChangedEventArgs" /> instance containing the event data.</param> void libraryManager_LibraryChanged(object sender, ChildrenChangedEventArgs e) { - _serverManager.SendWebSocketMessage("LibraryChanged", () => DtoBuilder.GetLibraryUpdateInfo(e)); + lock (_libraryChangedSyncLock) + { + if (LibraryUpdateInfo == null) + { + LibraryUpdateInfo = new LibraryUpdateInfo(); + } + + if (LibraryUpdateTimer == null) + { + LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, + Timeout.Infinite); + } + else + { + LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite); + } + + LibraryUpdateInfo.Folders.Add(e.Folder.Id); + + LibraryUpdateInfo.ItemsAdded.AddRange(e.ItemsAdded.Select(i => i.Id)); + LibraryUpdateInfo.ItemsUpdated.AddRange(e.ItemsUpdated.Select(i => i.Id)); + LibraryUpdateInfo.ItemsRemoved.AddRange(e.ItemsRemoved.Select(i => i.Id)); + } + } + + /// <summary> + /// Libraries the update timer callback. + /// </summary> + /// <param name="state">The state.</param> + private void LibraryUpdateTimerCallback(object state) + { + lock (_libraryChangedSyncLock) + { + _serverManager.SendWebSocketMessage("LibraryChanged", LibraryUpdateInfo); + + if (LibraryUpdateTimer != null) + { + LibraryUpdateTimer.Dispose(); + LibraryUpdateTimer = null; + } + } } /// <summary> @@ -206,6 +274,12 @@ namespace MediaBrowser.ServerApplication.EntryPoints { if (dispose) { + if (LibraryUpdateTimer != null) + { + LibraryUpdateTimer.Dispose(); + LibraryUpdateTimer = null; + } + _userManager.UserDeleted -= userManager_UserDeleted; _userManager.UserUpdated -= userManager_UserUpdated; diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 830fbe16d..d43ba947a 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -205,4 +205,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal |
