UserDataChangeNotifier.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using MediaBrowser.Controller.Entities;
  7. using MediaBrowser.Controller.Library;
  8. using MediaBrowser.Controller.Session;
  9. using MediaBrowser.Model.Entities;
  10. using MediaBrowser.Model.Session;
  11. using Microsoft.Extensions.Hosting;
  12. namespace Emby.Server.Implementations.EntryPoints
  13. {
  14. /// <summary>
  15. /// <see cref="IHostedService"/> responsible for notifying users when associated item data is updated.
  16. /// </summary>
  17. public sealed class UserDataChangeNotifier : IHostedService, IDisposable
  18. {
  19. private const int UpdateDuration = 500;
  20. private readonly ISessionManager _sessionManager;
  21. private readonly IUserDataManager _userDataManager;
  22. private readonly IUserManager _userManager;
  23. private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new();
  24. private readonly object _syncLock = new();
  25. private Timer? _updateTimer;
  26. /// <summary>
  27. /// Initializes a new instance of the <see cref="UserDataChangeNotifier"/> class.
  28. /// </summary>
  29. /// <param name="userDataManager">The <see cref="IUserDataManager"/>.</param>
  30. /// <param name="sessionManager">The <see cref="ISessionManager"/>.</param>
  31. /// <param name="userManager">The <see cref="IUserManager"/>.</param>
  32. public UserDataChangeNotifier(
  33. IUserDataManager userDataManager,
  34. ISessionManager sessionManager,
  35. IUserManager userManager)
  36. {
  37. _userDataManager = userDataManager;
  38. _sessionManager = sessionManager;
  39. _userManager = userManager;
  40. }
  41. /// <inheritdoc />
  42. public Task StartAsync(CancellationToken cancellationToken)
  43. {
  44. _userDataManager.UserDataSaved += OnUserDataManagerUserDataSaved;
  45. return Task.CompletedTask;
  46. }
  47. /// <inheritdoc />
  48. public Task StopAsync(CancellationToken cancellationToken)
  49. {
  50. _userDataManager.UserDataSaved -= OnUserDataManagerUserDataSaved;
  51. return Task.CompletedTask;
  52. }
  53. private void OnUserDataManagerUserDataSaved(object? sender, UserDataSaveEventArgs e)
  54. {
  55. if (e.SaveReason == UserDataSaveReason.PlaybackProgress)
  56. {
  57. return;
  58. }
  59. lock (_syncLock)
  60. {
  61. if (_updateTimer is null)
  62. {
  63. _updateTimer = new Timer(
  64. UpdateTimerCallback,
  65. null,
  66. UpdateDuration,
  67. Timeout.Infinite);
  68. }
  69. else
  70. {
  71. _updateTimer.Change(UpdateDuration, Timeout.Infinite);
  72. }
  73. if (!_changedItems.TryGetValue(e.UserId, out List<BaseItem>? keys))
  74. {
  75. keys = new List<BaseItem>();
  76. _changedItems[e.UserId] = keys;
  77. }
  78. keys.Add(e.Item);
  79. var baseItem = e.Item;
  80. // Go up one level for indicators
  81. if (baseItem is not null)
  82. {
  83. var parent = baseItem.GetOwner() ?? baseItem.GetParent();
  84. if (parent is not null)
  85. {
  86. keys.Add(parent);
  87. }
  88. }
  89. }
  90. }
  91. private async void UpdateTimerCallback(object? state)
  92. {
  93. List<KeyValuePair<Guid, List<BaseItem>>> changes;
  94. lock (_syncLock)
  95. {
  96. // Remove dupes in case some were saved multiple times
  97. changes = _changedItems.ToList();
  98. _changedItems.Clear();
  99. if (_updateTimer is not null)
  100. {
  101. _updateTimer.Dispose();
  102. _updateTimer = null;
  103. }
  104. }
  105. foreach (var (userId, changedItems) in changes)
  106. {
  107. await _sessionManager.SendMessageToUserSessions(
  108. [userId],
  109. SessionMessageType.UserDataChanged,
  110. () => GetUserDataChangeInfo(userId, changedItems),
  111. default).ConfigureAwait(false);
  112. }
  113. }
  114. private UserDataChangeInfo GetUserDataChangeInfo(Guid userId, List<BaseItem> changedItems)
  115. {
  116. var user = _userManager.GetUserById(userId)
  117. ?? throw new ArgumentException("Invalid user ID", nameof(userId));
  118. return new UserDataChangeInfo
  119. {
  120. UserId = userId,
  121. UserDataList = changedItems
  122. .DistinctBy(x => x.Id)
  123. .Select(i =>
  124. {
  125. var dto = _userDataManager.GetUserDataDto(i, user);
  126. dto.ItemId = i.Id;
  127. return dto;
  128. })
  129. .ToArray()
  130. };
  131. }
  132. /// <inheritdoc />
  133. public void Dispose()
  134. {
  135. _updateTimer?.Dispose();
  136. _updateTimer = null;
  137. }
  138. }
  139. }