From 16b9d26ab5e52c3c72dd24f17587ca4775ff79dd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 3 Oct 2013 14:02:23 -0400 Subject: fixes #273 - Marking/unmarking Favorite status doesn't cause a library changed notification --- .../Dto/DtoService.cs | 3 +- .../EntryPoints/ServerEventNotifier.cs | 188 +++++++++++++++++++++ .../EntryPoints/UserDataChangeNotifier.cs | 140 +++++++++++++++ .../EntryPoints/WebSocketEvents.cs | 188 --------------------- .../MediaBrowser.Server.Implementations.csproj | 3 +- .../Session/SessionManager.cs | 62 ++++++- .../Session/SessionWebSocketListener.cs | 13 +- .../Session/WebSocketController.cs | 55 +++++- 8 files changed, 455 insertions(+), 197 deletions(-) create mode 100644 MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs create mode 100644 MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs delete mode 100644 MediaBrowser.Server.Implementations/EntryPoints/WebSocketEvents.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 00808ad33..3c3e01151 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -340,7 +340,8 @@ namespace MediaBrowser.Server.Implementations.Dto PlayCount = data.PlayCount, Rating = data.Rating, Played = data.Played, - LastPlayedDate = data.LastPlayedDate + LastPlayedDate = data.LastPlayedDate, + Key = data.Key }; } private void SetBookProperties(BaseItemDto dto, Book item) diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs new file mode 100644 index 000000000..0925ca86c --- /dev/null +++ b/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -0,0 +1,188 @@ +using MediaBrowser.Common.Events; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Tasks; +using System; +using System.Threading; + +namespace MediaBrowser.Server.Implementations.EntryPoints +{ + /// + /// Class WebSocketEvents + /// + public class ServerEventNotifier : IServerEntryPoint + { + /// + /// The _server manager + /// + private readonly IServerManager _serverManager; + + /// + /// The _user manager + /// + private readonly IUserManager _userManager; + + /// + /// The _installation manager + /// + private readonly IInstallationManager _installationManager; + + /// + /// The _kernel + /// + private readonly IServerApplicationHost _appHost; + + /// + /// The _task manager + /// + private readonly ITaskManager _taskManager; + + private readonly IDtoService _dtoService; + + private readonly ISessionManager _sessionManager; + + /// + /// Initializes a new instance of the class. + /// + /// The server manager. + /// The logger. + /// The user manager. + public ServerEventNotifier(IServerManager serverManager, IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, IDtoService dtoService, ISessionManager sessionManager) + { + _serverManager = serverManager; + _userManager = userManager; + _installationManager = installationManager; + _appHost = appHost; + _taskManager = taskManager; + _dtoService = dtoService; + _sessionManager = sessionManager; + } + + public void Run() + { + _userManager.UserDeleted += userManager_UserDeleted; + _userManager.UserUpdated += userManager_UserUpdated; + + _appHost.HasPendingRestartChanged += kernel_HasPendingRestartChanged; + + _installationManager.PluginUninstalled += InstallationManager_PluginUninstalled; + _installationManager.PackageInstalling += _installationManager_PackageInstalling; + _installationManager.PackageInstallationCancelled += _installationManager_PackageInstallationCancelled; + _installationManager.PackageInstallationCompleted += _installationManager_PackageInstallationCompleted; + _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; + + _taskManager.TaskExecuting += _taskManager_TaskExecuting; + _taskManager.TaskCompleted += _taskManager_TaskCompleted; + } + + void _installationManager_PackageInstalling(object sender, InstallationEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstalling", e.InstallationInfo); + } + + void _installationManager_PackageInstallationCancelled(object sender, InstallationEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstallationCancelled", e.InstallationInfo); + } + + void _installationManager_PackageInstallationCompleted(object sender, InstallationEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstallationCompleted", e.InstallationInfo); + } + + void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) + { + _serverManager.SendWebSocketMessage("PackageInstallationFailed", e.InstallationInfo); + } + + void _taskManager_TaskCompleted(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("ScheduledTaskEnded", e.Argument); + } + + void _taskManager_TaskExecuting(object sender, EventArgs e) + { + var task = (IScheduledTask)sender; + _serverManager.SendWebSocketMessage("ScheduledTaskStarted", task.Name); + } + + /// + /// Installations the manager_ plugin uninstalled. + /// + /// The sender. + /// The e. + void InstallationManager_PluginUninstalled(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("PluginUninstalled", e.Argument.GetPluginInfo()); + } + + /// + /// Handles the HasPendingRestartChanged event of the kernel control. + /// + /// The source of the event. + /// The instance containing the event data. + void kernel_HasPendingRestartChanged(object sender, EventArgs e) + { + _sessionManager.SendRestartRequiredNotification(CancellationToken.None); + } + + /// + /// Users the manager_ user updated. + /// + /// The sender. + /// The e. + void userManager_UserUpdated(object sender, GenericEventArgs e) + { + var dto = _dtoService.GetUserDto(e.Argument); + + _serverManager.SendWebSocketMessage("UserUpdated", dto); + } + + /// + /// Users the manager_ user deleted. + /// + /// The sender. + /// The e. + void userManager_UserDeleted(object sender, GenericEventArgs e) + { + _serverManager.SendWebSocketMessage("UserDeleted", e.Argument.Id.ToString("N")); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// 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) + { + if (dispose) + { + _userManager.UserDeleted -= userManager_UserDeleted; + _userManager.UserUpdated -= userManager_UserUpdated; + + _installationManager.PluginUninstalled -= InstallationManager_PluginUninstalled; + _installationManager.PackageInstalling -= _installationManager_PackageInstalling; + _installationManager.PackageInstallationCancelled -= _installationManager_PackageInstallationCancelled; + _installationManager.PackageInstallationCompleted -= _installationManager_PackageInstallationCompleted; + _installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed; + + _appHost.HasPendingRestartChanged -= kernel_HasPendingRestartChanged; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs new file mode 100644 index 000000000..fc35e040d --- /dev/null +++ b/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -0,0 +1,140 @@ +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Session; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.EntryPoints +{ + class UserDataChangeNotifier : IServerEntryPoint + { + private readonly ISessionManager _sessionManager; + private readonly ILogger _logger; + private readonly IDtoService _dtoService; + private readonly IUserDataManager _userDataManager; + + private readonly object _syncLock = new object(); + private Timer UpdateTimer { get; set; } + private const int UpdateDuration = 2000; + + private readonly Dictionary> _changedKeys = new Dictionary>(); + + public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IDtoService dtoService, ILogger logger) + { + _userDataManager = userDataManager; + _sessionManager = sessionManager; + _dtoService = dtoService; + _logger = logger; + } + + public void Run() + { + _userDataManager.UserDataSaved += _userDataManager_UserDataSaved; + } + + void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e) + { + if (e.SaveReason == UserDataSaveReason.PlaybackProgress) + { + return; + } + + lock (_syncLock) + { + if (UpdateTimer == null) + { + UpdateTimer = new Timer(UpdateTimerCallback, null, UpdateDuration, + Timeout.Infinite); + } + else + { + UpdateTimer.Change(UpdateDuration, Timeout.Infinite); + } + + List keys; + + if (!_changedKeys.TryGetValue(e.UserId, out keys)) + { + keys = new List(); + _changedKeys[e.UserId] = keys; + } + + keys.Add(e.Key); + } + } + + private void UpdateTimerCallback(object state) + { + lock (_syncLock) + { + // Remove dupes in case some were saved multiple times + var changes = _changedKeys.ToList(); + _changedKeys.Clear(); + + SendNotifications(changes, CancellationToken.None); + + if (UpdateTimer != null) + { + UpdateTimer.Dispose(); + UpdateTimer = null; + } + } + } + + private async Task SendNotifications(List>> changes, CancellationToken cancellationToken) + { + foreach (var pair in changes) + { + var userId = pair.Key; + var userSessions = _sessionManager.Sessions + .Where(u => u.User != null && u.User.Id == userId && u.SessionController != null && u.IsActive) + .ToList(); + + if (userSessions.Count > 0) + { + var dtoList = pair.Value + .Select(i => _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(userId, i))) + .ToList(); + + var info = new UserDataChangeInfo + { + UserId = userId.ToString("N"), + + UserDataList = dtoList + }; + + foreach (var userSession in userSessions) + { + try + { + await userSession.SessionController.SendUserDataChangeInfo(info, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending UserDataChanged message", ex); + } + } + } + + } + } + + public void Dispose() + { + if (UpdateTimer != null) + { + UpdateTimer.Dispose(); + UpdateTimer = null; + } + + _userDataManager.UserDataSaved -= _userDataManager_UserDataSaved; + } + } +} diff --git a/MediaBrowser.Server.Implementations/EntryPoints/WebSocketEvents.cs b/MediaBrowser.Server.Implementations/EntryPoints/WebSocketEvents.cs deleted file mode 100644 index 4349b6976..000000000 --- a/MediaBrowser.Server.Implementations/EntryPoints/WebSocketEvents.cs +++ /dev/null @@ -1,188 +0,0 @@ -using MediaBrowser.Common.Events; -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Common.Updates; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Tasks; -using System; -using System.Threading; - -namespace MediaBrowser.Server.Implementations.EntryPoints -{ - /// - /// Class WebSocketEvents - /// - public class WebSocketEvents : IServerEntryPoint - { - /// - /// The _server manager - /// - private readonly IServerManager _serverManager; - - /// - /// The _user manager - /// - private readonly IUserManager _userManager; - - /// - /// The _installation manager - /// - private readonly IInstallationManager _installationManager; - - /// - /// The _kernel - /// - private readonly IServerApplicationHost _appHost; - - /// - /// The _task manager - /// - private readonly ITaskManager _taskManager; - - private readonly IDtoService _dtoService; - - private ISessionManager _sessionManager; - - /// - /// Initializes a new instance of the class. - /// - /// The server manager. - /// The logger. - /// The user manager. - public WebSocketEvents(IServerManager serverManager, IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, IDtoService dtoService, ISessionManager sessionManager) - { - _serverManager = serverManager; - _userManager = userManager; - _installationManager = installationManager; - _appHost = appHost; - _taskManager = taskManager; - _dtoService = dtoService; - _sessionManager = sessionManager; - } - - public void Run() - { - _userManager.UserDeleted += userManager_UserDeleted; - _userManager.UserUpdated += userManager_UserUpdated; - - _appHost.HasPendingRestartChanged += kernel_HasPendingRestartChanged; - - _installationManager.PluginUninstalled += InstallationManager_PluginUninstalled; - _installationManager.PackageInstalling += _installationManager_PackageInstalling; - _installationManager.PackageInstallationCancelled += _installationManager_PackageInstallationCancelled; - _installationManager.PackageInstallationCompleted += _installationManager_PackageInstallationCompleted; - _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; - - _taskManager.TaskExecuting += _taskManager_TaskExecuting; - _taskManager.TaskCompleted += _taskManager_TaskCompleted; - } - - void _installationManager_PackageInstalling(object sender, InstallationEventArgs e) - { - _serverManager.SendWebSocketMessage("PackageInstalling", e.InstallationInfo); - } - - void _installationManager_PackageInstallationCancelled(object sender, InstallationEventArgs e) - { - _serverManager.SendWebSocketMessage("PackageInstallationCancelled", e.InstallationInfo); - } - - void _installationManager_PackageInstallationCompleted(object sender, InstallationEventArgs e) - { - _serverManager.SendWebSocketMessage("PackageInstallationCompleted", e.InstallationInfo); - } - - void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) - { - _serverManager.SendWebSocketMessage("PackageInstallationFailed", e.InstallationInfo); - } - - void _taskManager_TaskCompleted(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("ScheduledTaskEnded", e.Argument); - } - - void _taskManager_TaskExecuting(object sender, EventArgs e) - { - var task = (IScheduledTask)sender; - _serverManager.SendWebSocketMessage("ScheduledTaskStarted", task.Name); - } - - /// - /// Installations the manager_ plugin uninstalled. - /// - /// The sender. - /// The e. - void InstallationManager_PluginUninstalled(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("PluginUninstalled", e.Argument.GetPluginInfo()); - } - - /// - /// Handles the HasPendingRestartChanged event of the kernel control. - /// - /// The source of the event. - /// The instance containing the event data. - void kernel_HasPendingRestartChanged(object sender, EventArgs e) - { - _sessionManager.SendRestartRequiredMessage(CancellationToken.None); - } - - /// - /// Users the manager_ user updated. - /// - /// The sender. - /// The e. - void userManager_UserUpdated(object sender, GenericEventArgs e) - { - var dto = _dtoService.GetUserDto(e.Argument); - - _serverManager.SendWebSocketMessage("UserUpdated", dto); - } - - /// - /// Users the manager_ user deleted. - /// - /// The sender. - /// The e. - void userManager_UserDeleted(object sender, GenericEventArgs e) - { - _serverManager.SendWebSocketMessage("UserDeleted", e.Argument.Id.ToString("N")); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// 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) - { - if (dispose) - { - _userManager.UserDeleted -= userManager_UserDeleted; - _userManager.UserUpdated -= userManager_UserUpdated; - - _installationManager.PluginUninstalled -= InstallationManager_PluginUninstalled; - _installationManager.PackageInstalling -= _installationManager_PackageInstalling; - _installationManager.PackageInstallationCancelled -= _installationManager_PackageInstallationCancelled; - _installationManager.PackageInstallationCompleted -= _installationManager_PackageInstallationCompleted; - _installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed; - - _appHost.HasPendingRestartChanged -= kernel_HasPendingRestartChanged; - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index b0f7553ea..86ea3b051 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -115,7 +115,8 @@ - + + diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index ac69b0dc5..6bb6edf7a 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -290,6 +290,7 @@ namespace MediaBrowser.Server.Implementations.Session var data = _userDataRepository.GetUserData(user.Id, key); UpdatePlayState(info.Item, data, info.PositionTicks.Value); + await _userDataRepository.SaveUserData(user.Id, key, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false); } @@ -501,7 +502,62 @@ namespace MediaBrowser.Server.Implementations.Session return session.SessionController.SendPlaystateCommand(command, cancellationToken); } - public Task SendRestartRequiredMessage(CancellationToken cancellationToken) + /// + /// Sends the restart required message. + /// + /// The cancellation token. + /// Task. + public Task SendRestartRequiredNotification(CancellationToken cancellationToken) + { + var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); + + var tasks = sessions.Select(session => Task.Run(async () => + { + try + { + await session.SessionController.SendRestartRequiredNotification(cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error in SendRestartRequiredNotification.", ex); + } + + })); + + return Task.WhenAll(tasks); + } + + /// + /// Sends the server shutdown notification. + /// + /// The cancellation token. + /// Task. + public Task SendServerShutdownNotification(CancellationToken cancellationToken) + { + var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); + + var tasks = sessions.Select(session => Task.Run(async () => + { + try + { + await session.SessionController.SendServerShutdownNotification(cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error in SendServerShutdownNotification.", ex); + } + + })); + + return Task.WhenAll(tasks); + } + + /// + /// Sends the server restart notification. + /// + /// The cancellation token. + /// Task. + public Task SendServerRestartNotification(CancellationToken cancellationToken) { var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); @@ -509,11 +565,11 @@ namespace MediaBrowser.Server.Implementations.Session { try { - await session.SessionController.SendRestartRequiredMessage(cancellationToken).ConfigureAwait(false); + await session.SessionController.SendServerRestartNotification(cancellationToken).ConfigureAwait(false); } catch (Exception ex) { - _logger.ErrorException("Error in SendRestartRequiredMessage.", ex); + _logger.ErrorException("Error in SendServerRestartNotification.", ex); } })); diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs index e90dd8eb9..399cce945 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs @@ -84,15 +84,22 @@ namespace MediaBrowser.Server.Implementations.Session /// Processes the identity message. /// /// The message. - private void ProcessIdentityMessage(WebSocketMessageInfo message) + private async void ProcessIdentityMessage(WebSocketMessageInfo message) { - _logger.Debug("Received Identity message"); + _logger.Debug("Received Identity message: " + message.Data); var vals = message.Data.Split('|'); var client = vals[0]; var deviceId = vals[1]; var version = vals[2]; + var deviceName = vals.Length > 3 ? vals[3] : string.Empty; + + if (!string.IsNullOrEmpty(deviceName)) + { + _logger.Debug("Logging session activity"); + await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, null).ConfigureAwait(false); + } var session = _sessionManager.Sessions .FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) && @@ -156,7 +163,7 @@ namespace MediaBrowser.Server.Implementations.Session if (result == null) { - _logger.Error("Unable to session based on web socket message"); + _logger.Error("Unable to find session based on web socket message"); } return result; diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs index 46c8f752d..6bbebf156 100644 --- a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs +++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs @@ -134,7 +134,7 @@ namespace MediaBrowser.Server.Implementations.Session /// /// The cancellation token. /// Task. - public Task SendRestartRequiredMessage(CancellationToken cancellationToken) + public Task SendRestartRequiredNotification(CancellationToken cancellationToken) { var socket = GetActiveSocket(); @@ -145,5 +145,58 @@ namespace MediaBrowser.Server.Implementations.Session }, cancellationToken); } + + + /// + /// Sends the user data change info. + /// + /// The info. + /// The cancellation token. + /// Task. + public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken) + { + var socket = GetActiveSocket(); + + return socket.SendAsync(new WebSocketMessage + { + MessageType = "UserDataChanged", + Data = info + + }, cancellationToken); + } + + /// + /// Sends the server shutdown notification. + /// + /// The cancellation token. + /// Task. + public Task SendServerShutdownNotification(CancellationToken cancellationToken) + { + var socket = GetActiveSocket(); + + return socket.SendAsync(new WebSocketMessage + { + MessageType = "ServerShuttingDown", + Data = string.Empty + + }, cancellationToken); + } + + /// + /// Sends the server restart notification. + /// + /// The cancellation token. + /// Task. + public Task SendServerRestartNotification(CancellationToken cancellationToken) + { + var socket = GetActiveSocket(); + + return socket.SendAsync(new WebSocketMessage + { + MessageType = "ServerRestarting", + Data = string.Empty + + }, cancellationToken); + } } } -- cgit v1.2.3