diff options
17 files changed, 352 insertions, 22 deletions
diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index 597a14cc6..21206af75 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -72,6 +72,28 @@ namespace MediaBrowser.Controller.Session /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendRestartRequiredMessage(CancellationToken cancellationToken); + Task SendRestartRequiredNotification(CancellationToken cancellationToken); + + /// <summary> + /// Sends the user data change info. + /// </summary> + /// <param name="info">The info.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken); + + /// <summary> + /// Sends the server shutdown notification. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task SendServerShutdownNotification(CancellationToken cancellationToken); + + /// <summary> + /// Sends the server restart notification. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task SendServerRestartNotification(CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index b2d111e54..771d8f72e 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -119,6 +119,20 @@ namespace MediaBrowser.Controller.Session /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendRestartRequiredMessage(CancellationToken cancellationToken); + Task SendRestartRequiredNotification(CancellationToken cancellationToken); + + /// <summary> + /// Sends the server shutdown notification. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task SendServerShutdownNotification(CancellationToken cancellationToken); + + /// <summary> + /// Sends the server restart notification. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task SendServerRestartNotification(CancellationToken cancellationToken); } }
\ No newline at end of file diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 60eacd91c..907433a80 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -350,6 +350,9 @@ <Compile Include="..\MediaBrowser.Model\Session\SystemCommand.cs"> <Link>Session\SystemCommand.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs"> + <Link>Session\UserDataChangeInfo.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\System\SystemInfo.cs"> <Link>System\SystemInfo.cs</Link> </Compile> diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 70cd4533d..076778d33 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -334,6 +334,9 @@ <Compile Include="..\MediaBrowser.Model\Session\SystemCommand.cs"> <Link>Session\SystemCommand.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs"> + <Link>Session\UserDataChangeInfo.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\System\SystemInfo.cs"> <Link>System\SystemInfo.cs</Link> </Compile> diff --git a/MediaBrowser.Model/Dto/UserItemDataDto.cs b/MediaBrowser.Model/Dto/UserItemDataDto.cs index e1b534eb1..8e8e7adbe 100644 --- a/MediaBrowser.Model/Dto/UserItemDataDto.cs +++ b/MediaBrowser.Model/Dto/UserItemDataDto.cs @@ -50,6 +50,12 @@ namespace MediaBrowser.Model.Dto /// <value><c>true</c> if played; otherwise, <c>false</c>.</value> public bool Played { get; set; } + /// <summary> + /// Gets or sets the key. + /// </summary> + /// <value>The key.</value> + public string Key { get; set; } + public event PropertyChangedEventHandler PropertyChanged; } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index e83df5354..ccb622111 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -126,6 +126,7 @@ <Compile Include="Serialization\IXmlSerializer.cs" /> <Compile Include="Session\SessionInfoDto.cs" /> <Compile Include="Session\SystemCommand.cs" /> + <Compile Include="Session\UserDataChangeInfo.cs" /> <Compile Include="Updates\CheckForUpdateResult.cs" /> <Compile Include="Updates\PackageTargetSystem.cs" /> <Compile Include="Updates\InstallationInfo.cs" /> diff --git a/MediaBrowser.Model/Session/UserDataChangeInfo.cs b/MediaBrowser.Model/Session/UserDataChangeInfo.cs new file mode 100644 index 000000000..f92f44586 --- /dev/null +++ b/MediaBrowser.Model/Session/UserDataChangeInfo.cs @@ -0,0 +1,23 @@ +using MediaBrowser.Model.Dto; +using System.Collections.Generic; + +namespace MediaBrowser.Model.Session +{ + /// <summary> + /// Class UserDataChangeInfo + /// </summary> + public class UserDataChangeInfo + { + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + public string UserId { get; set; } + + /// <summary> + /// Gets or sets the user data list. + /// </summary> + /// <value>The user data list.</value> + public List<UserItemDataDto> UserDataList { get; set; } + } +} 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/WebSocketEvents.cs b/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 4349b6976..0925ca86c 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/WebSocketEvents.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints /// <summary> /// Class WebSocketEvents /// </summary> - public class WebSocketEvents : IServerEntryPoint + public class ServerEventNotifier : IServerEntryPoint { /// <summary> /// The _server manager @@ -47,15 +47,15 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private readonly IDtoService _dtoService; - private ISessionManager _sessionManager; + private readonly ISessionManager _sessionManager; /// <summary> - /// Initializes a new instance of the <see cref="WebSocketEvents" /> class. + /// Initializes a new instance of the <see cref="ServerEventNotifier" /> class. /// </summary> /// <param name="serverManager">The server manager.</param> /// <param name="logger">The logger.</param> /// <param name="userManager">The user manager.</param> - public WebSocketEvents(IServerManager serverManager, IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, IDtoService dtoService, ISessionManager sessionManager) + public ServerEventNotifier(IServerManager serverManager, IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, IDtoService dtoService, ISessionManager sessionManager) { _serverManager = serverManager; _userManager = userManager; @@ -131,7 +131,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> void kernel_HasPendingRestartChanged(object sender, EventArgs e) { - _sessionManager.SendRestartRequiredMessage(CancellationToken.None); + _sessionManager.SendRestartRequiredNotification(CancellationToken.None); } /// <summary> 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<Guid, List<string>> _changedKeys = new Dictionary<Guid, List<string>>(); + + 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<string> keys; + + if (!_changedKeys.TryGetValue(e.UserId, out keys)) + { + keys = new List<string>(); + _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<KeyValuePair<Guid, List<string>>> 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/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 @@ <Compile Include="EntryPoints\Notifications\WebSocketNotifier.cs" /> <Compile Include="EntryPoints\RefreshUsersMetadata.cs" /> <Compile Include="EntryPoints\UdpServerEntryPoint.cs" /> - <Compile Include="EntryPoints\WebSocketEvents.cs" /> + <Compile Include="EntryPoints\ServerEventNotifier.cs" /> + <Compile Include="EntryPoints\UserDataChangeNotifier.cs" /> <Compile Include="HttpServer\HttpResultFactory.cs" /> <Compile Include="HttpServer\HttpServer.cs" /> <Compile Include="HttpServer\NativeWebSocket.cs" /> 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) + /// <summary> + /// Sends the restart required message. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + 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); + } + + /// <summary> + /// Sends the server shutdown notification. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + 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); + } + + /// <summary> + /// Sends the server restart notification. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + 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. /// </summary> /// <param name="message">The message.</param> - 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 /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - public Task SendRestartRequiredMessage(CancellationToken cancellationToken) + public Task SendRestartRequiredNotification(CancellationToken cancellationToken) { var socket = GetActiveSocket(); @@ -145,5 +145,58 @@ namespace MediaBrowser.Server.Implementations.Session }, cancellationToken); } + + + /// <summary> + /// Sends the user data change info. + /// </summary> + /// <param name="info">The info.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken) + { + var socket = GetActiveSocket(); + + return socket.SendAsync(new WebSocketMessage<UserDataChangeInfo> + { + MessageType = "UserDataChanged", + Data = info + + }, cancellationToken); + } + + /// <summary> + /// Sends the server shutdown notification. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task SendServerShutdownNotification(CancellationToken cancellationToken) + { + var socket = GetActiveSocket(); + + return socket.SendAsync(new WebSocketMessage<string> + { + MessageType = "ServerShuttingDown", + Data = string.Empty + + }, cancellationToken); + } + + /// <summary> + /// Sends the server restart notification. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task SendServerRestartNotification(CancellationToken cancellationToken) + { + var socket = GetActiveSocket(); + + return socket.SendAsync(new WebSocketMessage<string> + { + MessageType = "ServerRestarting", + Data = string.Empty + + }, cancellationToken); + } } } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index a10b7c15b..87bd45e50 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -488,11 +488,11 @@ namespace MediaBrowser.ServerApplication { try { - await ServerManager.SendWebSocketMessageAsync("ServerRestarting", () => string.Empty, CancellationToken.None).ConfigureAwait(false); + await SessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { - Logger.ErrorException("Error sending server restart web socket message", ex); + Logger.ErrorException("Error sending server restart notification", ex); } NativeApp.Restart(); @@ -609,11 +609,11 @@ namespace MediaBrowser.ServerApplication { try { - await ServerManager.SendWebSocketMessageAsync("ServerShuttingDown", () => string.Empty, CancellationToken.None).ConfigureAwait(false); + await SessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { - Logger.ErrorException("Error sending server shutdown web socket message", ex); + Logger.ErrorException("Error sending server shutdown notification", ex); } NativeApp.Shutdown(); diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 0ef566fbf..38a8a3136 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -170,7 +170,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi webSocket.onopen = function () { setTimeout(function () { - self.sendWebSocketMessage("Identity", clientName + "|" + deviceId + "|" + applicationVersion); + self.sendWebSocketMessage("Identity", clientName + "|" + deviceId + "|" + applicationVersion + "|" + deviceName); $(self).trigger("websocketopen"); }, 500); diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 3e9d96452..aec335395 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="MediaBrowser.ApiClient.Javascript" version="3.0.178" targetFramework="net45" /> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.179" targetFramework="net45" /> <package id="ServiceStack.Common" version="3.9.62" targetFramework="net45" /> <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" /> </packages>
\ No newline at end of file |
