diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index be36bbd2c1..a83d7a4105 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -23,476 +19,382 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; -namespace Emby.Server.Implementations.EntryPoints +namespace Emby.Server.Implementations.EntryPoints; + +/// +/// A that notifies users when libraries are updated. +/// +public sealed class LibraryChangedNotifier : IServerEntryPoint { - public class LibraryChangedNotifier : IServerEntryPoint + private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _configurationManager; + private readonly IProviderManager _providerManager; + private readonly ISessionManager _sessionManager; + private readonly IUserManager _userManager; + private readonly ILogger _logger; + + private readonly object _libraryChangedSyncLock = new(); + private readonly List _foldersAddedTo = new(); + private readonly List _foldersRemovedFrom = new(); + private readonly List _itemsAdded = new(); + private readonly List _itemsRemoved = new(); + private readonly List _itemsUpdated = new(); + private readonly ConcurrentDictionary _lastProgressMessageTimes = new(); + + private Timer? _libraryUpdateTimer; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + public LibraryChangedNotifier( + ILibraryManager libraryManager, + IServerConfigurationManager configurationManager, + ISessionManager sessionManager, + IUserManager userManager, + ILogger logger, + IProviderManager providerManager) { - private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _configurationManager; - private readonly IProviderManager _providerManager; - private readonly ISessionManager _sessionManager; - private readonly IUserManager _userManager; - private readonly ILogger _logger; - - /// - /// The library changed sync lock. - /// - private readonly object _libraryChangedSyncLock = new object(); - - private readonly List _foldersAddedTo = new List(); - private readonly List _foldersRemovedFrom = new List(); - private readonly List _itemsAdded = new List(); - private readonly List _itemsRemoved = new List(); - private readonly List _itemsUpdated = new List(); - private readonly ConcurrentDictionary _lastProgressMessageTimes = new ConcurrentDictionary(); - - public LibraryChangedNotifier( - ILibraryManager libraryManager, - IServerConfigurationManager configurationManager, - ISessionManager sessionManager, - IUserManager userManager, - ILogger logger, - IProviderManager providerManager) - { - _libraryManager = libraryManager; - _configurationManager = configurationManager; - _sessionManager = sessionManager; - _userManager = userManager; - _logger = logger; - _providerManager = providerManager; - } + _libraryManager = libraryManager; + _configurationManager = configurationManager; + _sessionManager = sessionManager; + _userManager = userManager; + _logger = logger; + _providerManager = providerManager; + } - /// - /// Gets or sets the library update timer. - /// - /// The library update timer. - private Timer LibraryUpdateTimer { get; set; } + /// + public Task RunAsync() + { + _libraryManager.ItemAdded += OnLibraryItemAdded; + _libraryManager.ItemUpdated += OnLibraryItemUpdated; + _libraryManager.ItemRemoved += OnLibraryItemRemoved; - public Task RunAsync() - { - _libraryManager.ItemAdded += OnLibraryItemAdded; - _libraryManager.ItemUpdated += OnLibraryItemUpdated; - _libraryManager.ItemRemoved += OnLibraryItemRemoved; + _providerManager.RefreshCompleted += OnProviderRefreshCompleted; + _providerManager.RefreshStarted += OnProviderRefreshStarted; + _providerManager.RefreshProgress += OnProviderRefreshProgress; - _providerManager.RefreshCompleted += OnProviderRefreshCompleted; - _providerManager.RefreshStarted += OnProviderRefreshStarted; - _providerManager.RefreshProgress += OnProviderRefreshProgress; + return Task.CompletedTask; + } - return Task.CompletedTask; - } + private void OnProviderRefreshProgress(object? sender, GenericEventArgs> e) + { + var item = e.Argument.Item1; - private void OnProviderRefreshProgress(object sender, GenericEventArgs> e) + if (!EnableRefreshMessage(item)) { - var item = e.Argument.Item1; + return; + } - if (!EnableRefreshMessage(item)) + var progress = e.Argument.Item2; + + if (_lastProgressMessageTimes.TryGetValue(item.Id, out var lastMessageSendTime)) + { + if (progress > 0 && progress < 100 && (DateTime.UtcNow - lastMessageSendTime).TotalMilliseconds < 1000) { return; } + } - var progress = e.Argument.Item2; + _lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow); - if (_lastProgressMessageTimes.TryGetValue(item.Id, out var lastMessageSendTime)) - { - if (progress > 0 && progress < 100 && (DateTime.UtcNow - lastMessageSendTime).TotalMilliseconds < 1000) - { - return; - } - } + var dict = new Dictionary(); + dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture); + dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture); - _lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow); + try + { + _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None); + } + catch + { + } - var dict = new Dictionary(); - dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture); - dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture); + var collectionFolders = _libraryManager.GetCollectionFolders(item); + + foreach (var collectionFolder in collectionFolders) + { + var collectionFolderDict = new Dictionary + { + ["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture), + ["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture) + }; try { - _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None); + _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None); } catch { } + } + } - var collectionFolders = _libraryManager.GetCollectionFolders(item); + private void OnProviderRefreshStarted(object? sender, GenericEventArgs e) + { + OnProviderRefreshProgress(sender, new GenericEventArgs>(new Tuple(e.Argument, 0))); + } - foreach (var collectionFolder in collectionFolders) - { - var collectionFolderDict = new Dictionary - { - ["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture), - ["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture) - }; - - try - { - _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None); - } - catch - { - } - } - } + private void OnProviderRefreshCompleted(object? sender, GenericEventArgs e) + { + OnProviderRefreshProgress(sender, new GenericEventArgs>(new Tuple(e.Argument, 100))); - private void OnProviderRefreshStarted(object sender, GenericEventArgs e) - { - OnProviderRefreshProgress(sender, new GenericEventArgs>(new Tuple(e.Argument, 0))); - } + _lastProgressMessageTimes.TryRemove(e.Argument.Id, out _); + } - private void OnProviderRefreshCompleted(object sender, GenericEventArgs e) - { - OnProviderRefreshProgress(sender, new GenericEventArgs>(new Tuple(e.Argument, 100))); + private static bool EnableRefreshMessage(BaseItem item) + => item is Folder { IsRoot: false, IsTopParent: true } + and not (AggregateFolder or UserRootFolder or UserView or Channel); - _lastProgressMessageTimes.TryRemove(e.Argument.Id, out _); - } + private void OnLibraryItemAdded(object? sender, ItemChangeEventArgs e) + => OnLibraryChange(e.Item, e.Parent, _itemsAdded, _foldersAddedTo); - private static bool EnableRefreshMessage(BaseItem item) + private void OnLibraryItemUpdated(object? sender, ItemChangeEventArgs e) + => OnLibraryChange(e.Item, e.Parent, _itemsUpdated, null); + + private void OnLibraryItemRemoved(object? sender, ItemChangeEventArgs e) + => OnLibraryChange(e.Item, e.Parent, _itemsRemoved, _foldersRemovedFrom); + + private void OnLibraryChange(BaseItem item, BaseItem parent, List itemsList, List? foldersList) + { + if (!FilterItem(item)) { - if (item is not Folder folder) - { - return false; - } + return; + } - if (folder.IsRoot) - { - return false; - } + lock (_libraryChangedSyncLock) + { + var updateDuration = TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration); - if (folder is AggregateFolder || folder is UserRootFolder) + if (_libraryUpdateTimer is null) { - return false; + _libraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, updateDuration, Timeout.InfiniteTimeSpan); } - - if (folder is UserView || folder is Channel) + else { - return false; + _libraryUpdateTimer.Change(updateDuration, Timeout.InfiniteTimeSpan); } - if (!folder.IsTopParent) + if (foldersList is not null && parent is Folder folder) { - return false; + foldersList.Add(folder); } - return true; + itemsList.Add(item); } + } - /// - /// Handles the ItemAdded event of the libraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - private void OnLibraryItemAdded(object sender, ItemChangeEventArgs e) + private async void LibraryUpdateTimerCallback(object? state) + { + List foldersAddedTo; + List foldersRemovedFrom; + List itemsUpdated; + List itemsAdded; + List itemsRemoved; + lock (_libraryChangedSyncLock) { - if (!FilterItem(e.Item)) - { - return; - } + // Remove dupes in case some were saved multiple times + foldersAddedTo = _foldersAddedTo + .DistinctBy(x => x.Id) + .ToList(); - lock (_libraryChangedSyncLock) + foldersRemovedFrom = _foldersRemovedFrom + .DistinctBy(x => x.Id) + .ToList(); + + itemsUpdated = _itemsUpdated + .Where(i => !_itemsAdded.Contains(i)) + .DistinctBy(x => x.Id) + .ToList(); + + itemsAdded = _itemsAdded.ToList(); + itemsRemoved = _itemsRemoved.ToList(); + + if (_libraryUpdateTimer is not null) { - if (LibraryUpdateTimer is null) - { - LibraryUpdateTimer = new Timer( - LibraryUpdateTimerCallback, - null, - TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), - Timeout.InfiniteTimeSpan); - } - else - { - LibraryUpdateTimer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan); - } - - if (e.Item.GetParent() is Folder parent) - { - _foldersAddedTo.Add(parent); - } - - _itemsAdded.Add(e.Item); + _libraryUpdateTimer.Dispose(); + _libraryUpdateTimer = null; } + + _itemsAdded.Clear(); + _itemsRemoved.Clear(); + _itemsUpdated.Clear(); + _foldersAddedTo.Clear(); + _foldersRemovedFrom.Clear(); } - /// - /// Handles the ItemUpdated event of the libraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - private void OnLibraryItemUpdated(object sender, ItemChangeEventArgs e) + await SendChangeNotifications(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, CancellationToken.None).ConfigureAwait(false); + } + + private async Task SendChangeNotifications( + List itemsAdded, + List itemsUpdated, + List itemsRemoved, + List foldersAddedTo, + List foldersRemovedFrom, + CancellationToken cancellationToken) + { + var userIds = _sessionManager.Sessions + .Select(i => i.UserId) + .Where(i => !i.Equals(default)) + .Distinct() + .ToArray(); + + foreach (var userId in userIds) { - if (!FilterItem(e.Item)) - { - return; - } + LibraryUpdateInfo info; - lock (_libraryChangedSyncLock) + try { - if (LibraryUpdateTimer is null) - { - LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan); - } - else - { - LibraryUpdateTimer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan); - } - - _itemsUpdated.Add(e.Item); + info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, userId); } - } - - /// - /// Handles the ItemRemoved event of the libraryManager control. - /// - /// The source of the event. - /// The instance containing the event data. - private void OnLibraryItemRemoved(object sender, ItemChangeEventArgs e) - { - if (!FilterItem(e.Item)) + catch (Exception ex) { + _logger.LogError(ex, "Error in GetLibraryUpdateInfo"); return; } - lock (_libraryChangedSyncLock) + if (info.IsEmpty) { - if (LibraryUpdateTimer is null) - { - LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan); - } - else - { - LibraryUpdateTimer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan); - } - - if (e.Parent is Folder parent) - { - _foldersRemovedFrom.Add(parent); - } - - _itemsRemoved.Add(e.Item); + continue; } - } - /// - /// Libraries the update timer callback. - /// - /// The state. - private async void LibraryUpdateTimerCallback(object state) - { - List foldersAddedTo; - List foldersRemovedFrom; - List itemsUpdated; - List itemsAdded; - List itemsRemoved; - lock (_libraryChangedSyncLock) + try { - // Remove dupes in case some were saved multiple times - foldersAddedTo = _foldersAddedTo - .DistinctBy(x => x.Id) - .ToList(); - - foldersRemovedFrom = _foldersRemovedFrom - .DistinctBy(x => x.Id) - .ToList(); - - itemsUpdated = _itemsUpdated - .Where(i => !_itemsAdded.Contains(i)) - .DistinctBy(x => x.Id) - .ToList(); - - itemsAdded = _itemsAdded.ToList(); - itemsRemoved = _itemsRemoved.ToList(); - - if (LibraryUpdateTimer is not null) - { - LibraryUpdateTimer.Dispose(); - LibraryUpdateTimer = null; - } - - _itemsAdded.Clear(); - _itemsRemoved.Clear(); - _itemsUpdated.Clear(); - _foldersAddedTo.Clear(); - _foldersRemovedFrom.Clear(); + await _sessionManager.SendMessageToUserSessions( + new List { userId }, + SessionMessageType.LibraryChanged, + info, + cancellationToken) + .ConfigureAwait(false); } - - await SendChangeNotifications(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, CancellationToken.None).ConfigureAwait(false); - } - - /// - /// Sends the change notifications. - /// - /// The items added. - /// The items updated. - /// The items removed. - /// The folders added to. - /// The folders removed from. - /// The cancellation token. - private async Task SendChangeNotifications(List itemsAdded, List itemsUpdated, List itemsRemoved, List foldersAddedTo, List foldersRemovedFrom, CancellationToken cancellationToken) - { - var userIds = _sessionManager.Sessions - .Select(i => i.UserId) - .Where(i => !i.Equals(default)) - .Distinct() - .ToArray(); - - foreach (var userId in userIds) + catch (Exception ex) { - LibraryUpdateInfo info; - - try - { - info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo, foldersRemovedFrom, userId); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in GetLibraryUpdateInfo"); - return; - } - - if (info.IsEmpty) - { - continue; - } - - try - { - await _sessionManager.SendMessageToUserSessions(new List { userId }, SessionMessageType.LibraryChanged, info, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error sending LibraryChanged message"); - } + _logger.LogError(ex, "Error sending LibraryChanged message"); } } + } - /// - /// Gets the library update info. - /// - /// The items added. - /// The items updated. - /// The items removed. - /// The folders added to. - /// The folders removed from. - /// The user id. - /// LibraryUpdateInfo. - private LibraryUpdateInfo GetLibraryUpdateInfo(List itemsAdded, List itemsUpdated, List itemsRemoved, List foldersAddedTo, List foldersRemovedFrom, Guid userId) - { - var user = _userManager.GetUserById(userId); - - var newAndRemoved = new List(); - newAndRemoved.AddRange(foldersAddedTo); - newAndRemoved.AddRange(foldersRemovedFrom); - - var allUserRootChildren = _libraryManager.GetUserRootFolder().GetChildren(user, true).OfType().ToList(); - - return new LibraryUpdateInfo - { - ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), - - ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), - - ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), + private LibraryUpdateInfo GetLibraryUpdateInfo( + List itemsAdded, + List itemsUpdated, + List itemsRemoved, + List foldersAddedTo, + List foldersRemovedFrom, + Guid userId) + { + var user = _userManager.GetUserById(userId); + ArgumentNullException.ThrowIfNull(user); - FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), + var newAndRemoved = new List(); + newAndRemoved.AddRange(foldersAddedTo); + newAndRemoved.AddRange(foldersRemovedFrom); - FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), + var allUserRootChildren = _libraryManager.GetUserRootFolder() + .GetChildren(user, true) + .OfType() + .ToList(); - CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray() - }; - } - - private static bool FilterItem(BaseItem item) + return new LibraryUpdateInfo { - if (!item.IsFolder && !item.HasPathProtocol) - { - return false; - } - - if (item is IItemByName && item is not MusicArtist) - { - return false; - } + ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .Distinct() + .ToArray(), + ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .Distinct() + .ToArray(), + ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .Distinct() + .ToArray(), + FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .Distinct() + .ToArray(), + FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .Distinct() + .ToArray(), + CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray() + }; + } - return item.SourceType == SourceType.Library; + private static bool FilterItem(BaseItem item) + { + if (!item.IsFolder && !item.HasPathProtocol) + { + return false; } - private IEnumerable GetTopParentIds(List items, List allUserRootChildren) + if (item is IItemByName && item is not MusicArtist) { - var list = new List(); + return false; + } - foreach (var item in items) - { - // If the physical root changed, return the user root - if (item is AggregateFolder) - { - continue; - } - - foreach (var folder in allUserRootChildren) - { - list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture)); - } - } + return item.SourceType == SourceType.Library; + } - return list.Distinct(StringComparer.Ordinal); - } + private IEnumerable GetTopParentIds(List items, List allUserRootChildren) + { + var list = new List(); - /// - /// Translates the physical item to user library. - /// - /// The type of item. - /// The item. - /// The user. - /// if set to true [include if not found]. - /// IEnumerable{``0}. - private IEnumerable TranslatePhysicalItemToUserLibrary(T item, User user, bool includeIfNotFound = false) - where T : BaseItem + foreach (var item in items) { // If the physical root changed, return the user root if (item is AggregateFolder) { - return new[] { _libraryManager.GetUserRootFolder() as T }; + continue; } - // Return it only if it's in the user's library - if (includeIfNotFound || item.IsVisibleStandalone(user)) + foreach (var folder in allUserRootChildren) { - return new[] { item }; + list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture)); } + } - return Array.Empty(); + return list.Distinct(StringComparer.Ordinal); + } + + private IEnumerable TranslatePhysicalItemToUserLibrary(T item, User user, bool includeIfNotFound = false) + where T : BaseItem + { + // If the physical root changed, return the user root + if (item is AggregateFolder) + { + return _libraryManager.GetUserRootFolder() is T t ? new[] { t } : Array.Empty(); } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() + // Return it only if it's in the user's library + if (includeIfNotFound || item.IsVisibleStandalone(user)) { - Dispose(true); - GC.SuppressFinalize(this); + return new[] { item }; } - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) + return Array.Empty(); + } + + /// + public void Dispose() + { + _libraryManager.ItemAdded -= OnLibraryItemAdded; + _libraryManager.ItemUpdated -= OnLibraryItemUpdated; + _libraryManager.ItemRemoved -= OnLibraryItemRemoved; + + _providerManager.RefreshCompleted -= OnProviderRefreshCompleted; + _providerManager.RefreshStarted -= OnProviderRefreshStarted; + _providerManager.RefreshProgress -= OnProviderRefreshProgress; + + if (_libraryUpdateTimer is not null) { - if (dispose) - { - if (LibraryUpdateTimer is not null) - { - LibraryUpdateTimer.Dispose(); - LibraryUpdateTimer = null; - } - - _libraryManager.ItemAdded -= OnLibraryItemAdded; - _libraryManager.ItemUpdated -= OnLibraryItemUpdated; - _libraryManager.ItemRemoved -= OnLibraryItemRemoved; - - _providerManager.RefreshCompleted -= OnProviderRefreshCompleted; - _providerManager.RefreshStarted -= OnProviderRefreshStarted; - _providerManager.RefreshProgress -= OnProviderRefreshProgress; - } + _libraryUpdateTimer.Dispose(); + _libraryUpdateTimer = null; } } }