diff options
| author | stefan <stefan@hegedues.at> | 2018-09-12 19:26:21 +0200 |
|---|---|---|
| committer | stefan <stefan@hegedues.at> | 2018-09-12 19:26:21 +0200 |
| commit | 48facb797ed912e4ea6b04b17d1ff190ac2daac4 (patch) | |
| tree | 8dae77a31670a888d733484cb17dd4077d5444e8 /Emby.Server.Implementations/Session | |
| parent | c32d8656382a0eacb301692e0084377fc433ae9b (diff) | |
Update to 3.5.2 and .net core 2.1
Diffstat (limited to 'Emby.Server.Implementations/Session')
5 files changed, 691 insertions, 1092 deletions
diff --git a/Emby.Server.Implementations/Session/FirebaseSessionController.cs b/Emby.Server.Implementations/Session/FirebaseSessionController.cs new file mode 100644 index 000000000..cfe513305 --- /dev/null +++ b/Emby.Server.Implementations/Session/FirebaseSessionController.cs @@ -0,0 +1,131 @@ +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Net; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Serialization; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Text; +using MediaBrowser.Common; + +namespace Emby.Server.Implementations.Session +{ + public class FirebaseSessionController : ISessionController + { + private readonly IHttpClient _httpClient; + private readonly IJsonSerializer _json; + private readonly ISessionManager _sessionManager; + + public SessionInfo Session { get; private set; } + + private readonly string _token; + + private IApplicationHost _appHost; + private string _senderId; + private string _applicationId; + + public FirebaseSessionController(IHttpClient httpClient, + IApplicationHost appHost, + IJsonSerializer json, + SessionInfo session, + string token, ISessionManager sessionManager) + { + _httpClient = httpClient; + _json = json; + _appHost = appHost; + Session = session; + _token = token; + _sessionManager = sessionManager; + + _applicationId = _appHost.GetValue("firebase_applicationid"); + _senderId = _appHost.GetValue("firebase_senderid"); + } + + public static bool IsSupported(IApplicationHost appHost) + { + return !string.IsNullOrEmpty(appHost.GetValue("firebase_applicationid")) && !string.IsNullOrEmpty(appHost.GetValue("firebase_senderid")); + } + + public bool IsSessionActive + { + get + { + return (DateTime.UtcNow - Session.LastActivityDate).TotalDays <= 3; + } + } + + public bool SupportsMediaControl + { + get { return true; } + } + + public async Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) + { + if (!IsSessionActive) + { + return; + } + + if (string.IsNullOrEmpty(_senderId) || string.IsNullOrEmpty(_applicationId)) + { + return; + } + + foreach (var controller in allControllers) + { + // Don't send if there's an active web socket connection + if ((controller is WebSocketController) && controller.IsSessionActive) + { + return; + } + } + + var msg = new WebSocketMessage<T> + { + Data = data, + MessageType = name, + MessageId = messageId, + ServerId = _appHost.SystemId + }; + + var req = new FirebaseBody<T> + { + to = _token, + data = msg + }; + + var byteArray = Encoding.UTF8.GetBytes(_json.SerializeToString(req)); + + var enableLogging = false; + +#if DEBUG + enableLogging = true; +#endif + + var options = new HttpRequestOptions + { + Url = "https://fcm.googleapis.com/fcm/send", + RequestContentType = "application/json", + RequestContentBytes = byteArray, + CancellationToken = cancellationToken, + LogRequest = enableLogging, + LogResponse = enableLogging, + LogErrors = enableLogging + }; + + options.RequestHeaders["Authorization"] = string.Format("key={0}", _applicationId); + options.RequestHeaders["Sender"] = string.Format("id={0}", _senderId); + + using (var response = await _httpClient.Post(options).ConfigureAwait(false)) + { + + } + } + } + + internal class FirebaseBody<T> + { + public string to { get; set; } + public WebSocketMessage<T> data { get; set; } + } +} diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index 6725cd7af..ff9b3fefc 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -36,10 +36,6 @@ namespace Emby.Server.Implementations.Session _sessionManager = sessionManager; } - public void OnActivity() - { - } - private string PostUrl { get @@ -52,7 +48,7 @@ namespace Emby.Server.Implementations.Session { get { - return (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 10; + return (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 5; } } @@ -61,49 +57,29 @@ namespace Emby.Server.Implementations.Session get { return true; } } - private Task SendMessage(string name, CancellationToken cancellationToken) + private Task SendMessage(string name, string messageId, CancellationToken cancellationToken) { - return SendMessage(name, new Dictionary<string, string>(), cancellationToken); + return SendMessage(name, messageId, new Dictionary<string, string>(), cancellationToken); } - private async Task SendMessage(string name, - Dictionary<string, string> args, - CancellationToken cancellationToken) + private Task SendMessage(string name, string messageId, Dictionary<string, string> args, CancellationToken cancellationToken) { + args["messageId"] = messageId; var url = PostUrl + "/" + name + ToQueryString(args); - using ((await _httpClient.Post(new HttpRequestOptions + return SendRequest(new HttpRequestOptions { Url = url, CancellationToken = cancellationToken, BufferContent = false - - }).ConfigureAwait(false))) - { - - } - } - - public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return Task.FromResult(true); + }); } - public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) + private Task SendPlayCommand(PlayRequest command, string messageId, CancellationToken cancellationToken) { var dict = new Dictionary<string, string>(); - dict["ItemIds"] = string.Join(",", command.ItemIds); + dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N")).ToArray()); if (command.StartPositionTicks.HasValue) { @@ -121,15 +97,15 @@ namespace Emby.Server.Implementations.Session { dict["StartIndex"] = command.StartIndex.Value.ToString(CultureInfo.InvariantCulture); } - if (!string.IsNullOrWhiteSpace(command.MediaSourceId)) + if (!string.IsNullOrEmpty(command.MediaSourceId)) { dict["MediaSourceId"] = command.MediaSourceId; } - return SendMessage(command.PlayCommand.ToString(), dict, cancellationToken); + return SendMessage(command.PlayCommand.ToString(), messageId, dict, cancellationToken); } - public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) + private Task SendPlaystateCommand(PlaystateRequest command, string messageId, CancellationToken cancellationToken) { var args = new Dictionary<string, string>(); @@ -143,60 +119,74 @@ namespace Emby.Server.Implementations.Session args["SeekPositionTicks"] = command.SeekPositionTicks.Value.ToString(CultureInfo.InvariantCulture); } - return SendMessage(command.Command.ToString(), args, cancellationToken); + return SendMessage(command.Command.ToString(), messageId, args, cancellationToken); } - public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken) + private string[] _supportedMessages = new string[] { }; + public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) { - return SendMessage("LibraryChanged", info, cancellationToken); - } + if (!IsSessionActive) + { + return Task.CompletedTask; + } - public Task SendRestartRequiredNotification(CancellationToken cancellationToken) - { - return SendMessage("RestartRequired", cancellationToken); - } + if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase)) + { + return SendPlayCommand(data as PlayRequest, messageId, cancellationToken); + } + if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) + { + return SendPlaystateCommand(data as PlaystateRequest, messageId, cancellationToken); + } + if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) + { + var command = data as GeneralCommand; + return SendMessage(command.Name, messageId, command.Arguments, cancellationToken); + } - public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } + if (!_supportedMessages.Contains(name, StringComparer.OrdinalIgnoreCase)) + { + return Task.CompletedTask; + } - public Task SendServerShutdownNotification(CancellationToken cancellationToken) - { - return SendMessage("ServerShuttingDown", cancellationToken); - } + var url = PostUrl + "/" + name; - public Task SendServerRestartNotification(CancellationToken cancellationToken) - { - return SendMessage("ServerRestarting", cancellationToken); - } + url += "?messageId=" + messageId; - public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) - { - return SendMessage(command.Name, command.Arguments, cancellationToken); + var options = new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + BufferContent = false + }; + + if (data != null) + { + if (typeof(T) == typeof(string)) + { + var str = data as String; + if (!string.IsNullOrEmpty(str)) + { + options.RequestContent = str; + options.RequestContentType = "application/json"; + } + } + else + { + options.RequestContent = _json.SerializeToString(data); + options.RequestContentType = "application/json"; + } + } + + return SendRequest(options); } - public Task SendMessage<T>(string name, T data, CancellationToken cancellationToken) + private async Task SendRequest(HttpRequestOptions options) { - return Task.FromResult(true); - //var url = PostUrl + "/" + name; - - //var options = new HttpRequestOptions - //{ - // Url = url, - // CancellationToken = cancellationToken, - // BufferContent = false - //}; - - //options.RequestContent = _json.SerializeToString(data); - //options.RequestContentType = "application/json"; - - //return _httpClient.Post(new HttpRequestOptions - //{ - // Url = url, - // CancellationToken = cancellationToken, - // BufferContent = false - //}); + using (var response = await _httpClient.Post(options).ConfigureAwait(false)) + { + + } } private string ToQueryString(Dictionary<string, string> nvc) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6b70f2cda..9db4f4423 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -30,13 +30,14 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Threading; using MediaBrowser.Model.Extensions; +using MediaBrowser.Controller.Authentication; namespace Emby.Server.Implementations.Session { /// <summary> /// Class SessionManager /// </summary> - public class SessionManager : ISessionManager + public class SessionManager : ISessionManager, IDisposable { /// <summary> /// The _user data repository @@ -71,7 +72,7 @@ namespace Emby.Server.Implementations.Session public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationFailed; - public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationSucceeded; + public event EventHandler<GenericEventArgs<AuthenticationResult>> AuthenticationSucceeded; /// <summary> /// Occurs when [playback start]. @@ -91,10 +92,6 @@ namespace Emby.Server.Implementations.Session public event EventHandler<SessionEventArgs> SessionEnded; public event EventHandler<SessionEventArgs> SessionActivity; - private IEnumerable<ISessionControllerFactory> _sessionFactories = new List<ISessionControllerFactory>(); - - private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); - public SessionManager(IUserDataManager userDataManager, ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, ITimerFactory timerFactory) { _userDataManager = userDataManager; @@ -111,28 +108,41 @@ namespace Emby.Server.Implementations.Session _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; _timerFactory = timerFactory; - _deviceManager.DeviceOptionsUpdated += _deviceManager_DeviceOptionsUpdated; } - void _deviceManager_DeviceOptionsUpdated(object sender, GenericEventArgs<DeviceInfo> e) + private void _deviceManager_DeviceOptionsUpdated(object sender, GenericEventArgs<Tuple<string, DeviceOptions>> e) { foreach (var session in Sessions) { - if (string.Equals(session.DeviceId, e.Argument.Id)) + if (string.Equals(session.DeviceId, e.Argument.Item1)) { - session.DeviceName = e.Argument.Name; + if (!string.IsNullOrWhiteSpace(e.Argument.Item2.CustomName)) + { + session.HasCustomDeviceName = true; + session.DeviceName = e.Argument.Item2.CustomName; + } + else + { + session.HasCustomDeviceName = false; + } } } } - /// <summary> - /// Adds the parts. - /// </summary> - /// <param name="sessionFactories">The session factories.</param> - public void AddParts(IEnumerable<ISessionControllerFactory> sessionFactories) + private bool _disposed; + public void Dispose() + { + _disposed = true; + _deviceManager.DeviceOptionsUpdated -= _deviceManager_DeviceOptionsUpdated; + } + + public void CheckDisposed() { - _sessionFactories = sessionFactories.ToList(); + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } } /// <summary> @@ -146,58 +156,44 @@ namespace Emby.Server.Implementations.Session private void OnSessionStarted(SessionInfo info) { - EventHelper.QueueEventIfNotNull(SessionStarted, this, new SessionEventArgs - { - SessionInfo = info - - }, _logger); - - if (!string.IsNullOrWhiteSpace(info.DeviceId)) + if (!string.IsNullOrEmpty(info.DeviceId)) { var capabilities = GetSavedCapabilities(info.DeviceId); if (capabilities != null) { - info.AppIconUrl = capabilities.IconUrl; ReportCapabilities(info, capabilities, false); } } - } - private async void OnSessionEnded(SessionInfo info) - { - try - { - await SendSessionEndedNotification(info, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) + EventHelper.QueueEventIfNotNull(SessionStarted, this, new SessionEventArgs { - _logger.ErrorException("Error in SendSessionEndedNotification", ex); - } + SessionInfo = info + }, _logger); + } + + private void OnSessionEnded(SessionInfo info) + { EventHelper.QueueEventIfNotNull(SessionEnded, this, new SessionEventArgs { SessionInfo = info }, _logger); - var disposable = info.SessionController as IDisposable; + info.Dispose(); + } - if (disposable != null) - { - _logger.Debug("Disposing session controller {0}", disposable.GetType().Name); + public void UpdateDeviceName(string sessionId, string deviceName) + { + var session = GetSession(sessionId); - try - { - disposable.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing session controller", ex); - } - } + var key = GetSessionKey(session.Client, session.DeviceId); - info.Dispose(); + if (session != null) + { + session.DeviceName = deviceName; + } } /// <summary> @@ -212,13 +208,15 @@ namespace Emby.Server.Implementations.Session /// <returns>Task.</returns> /// <exception cref="System.ArgumentNullException">user</exception> /// <exception cref="System.UnauthorizedAccessException"></exception> - public async Task<SessionInfo> LogSessionActivity(string appName, + public SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) { + CheckDisposed(); + if (string.IsNullOrEmpty(appName)) { throw new ArgumentNullException("appName"); @@ -231,13 +229,9 @@ namespace Emby.Server.Implementations.Session { throw new ArgumentNullException("deviceId"); } - if (string.IsNullOrEmpty(deviceName)) - { - throw new ArgumentNullException("deviceName"); - } var activityDate = DateTime.UtcNow; - var session = await GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); + var session = GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user); var lastActivityDate = session.LastActivityDate; session.LastActivityDate = activityDate; @@ -268,40 +262,39 @@ namespace Emby.Server.Implementations.Session }, _logger); } - var controller = session.SessionController; - if (controller != null) - { - controller.OnActivity(); - } - return session; } - public async void ReportSessionEnded(string sessionId) + public void CloseIfNeeded(SessionInfo session) { - await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - - try + if (!session.SessionControllers.Any(i => i.IsSessionActive)) { - var session = GetSession(sessionId, false); + var key = GetSessionKey(session.Client, session.DeviceId); - if (session != null) - { - var key = GetSessionKey(session.Client, session.DeviceId); - - SessionInfo removed; - _activeConnections.TryRemove(key, out removed); + SessionInfo removed; + _activeConnections.TryRemove(key, out removed); - OnSessionEnded(session); - } + OnSessionEnded(session); } - finally + } + + public void ReportSessionEnded(string sessionId) + { + CheckDisposed(); + var session = GetSession(sessionId, false); + + if (session != null) { - _sessionLock.Release(); + var key = GetSessionKey(session.Client, session.DeviceId); + + SessionInfo removed; + _activeConnections.TryRemove(key, out removed); + + OnSessionEnded(session); } } - private Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId) + private Task<MediaSourceInfo> GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId) { return _mediaSourceManager.GetMediaSource(item, mediaSourceId, liveStreamId, false, CancellationToken.None); } @@ -311,16 +304,16 @@ namespace Emby.Server.Implementations.Session /// </summary> private async Task UpdateNowPlayingItem(SessionInfo session, PlaybackProgressInfo info, BaseItem libraryItem, bool updateLastCheckInTime) { - if (string.IsNullOrWhiteSpace(info.MediaSourceId)) + if (string.IsNullOrEmpty(info.MediaSourceId)) { - info.MediaSourceId = info.ItemId; + info.MediaSourceId = info.ItemId.ToString("N"); } - if (!string.IsNullOrWhiteSpace(info.ItemId) && info.Item == null && libraryItem != null) + if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null) { var current = session.NowPlayingItem; - if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase)) + if (current == null || !info.ItemId.Equals(current.Id)) { var runtimeTicks = libraryItem.RunTimeTicks; @@ -328,7 +321,7 @@ namespace Emby.Server.Implementations.Session var hasMediaSources = libraryItem as IHasMediaSources; if (hasMediaSources != null) { - mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); + mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); if (mediaSource != null) { @@ -364,6 +357,14 @@ namespace Emby.Server.Implementations.Session session.PlayState.SubtitleStreamIndex = info.SubtitleStreamIndex; session.PlayState.PlayMethod = info.PlayMethod; session.PlayState.RepeatMode = info.RepeatMode; + session.PlaylistItemId = info.PlaylistItemId; + + var nowPlayingQueue = info.NowPlayingQueue; + + if (nowPlayingQueue != null) + { + session.NowPlayingQueue = nowPlayingQueue; + } } /// <summary> @@ -397,99 +398,89 @@ namespace Emby.Server.Implementations.Session /// <param name="remoteEndPoint">The remote end point.</param> /// <param name="user">The user.</param> /// <returns>SessionInfo.</returns> - private async Task<SessionInfo> GetSessionInfo(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + private SessionInfo GetSessionInfo(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) { - if (string.IsNullOrWhiteSpace(deviceId)) + CheckDisposed(); + + if (string.IsNullOrEmpty(deviceId)) { throw new ArgumentNullException("deviceId"); } var key = GetSessionKey(appName, deviceId); - await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + CheckDisposed(); - var userId = user == null ? (Guid?)null : user.Id; - var username = user == null ? null : user.Name; - - try + SessionInfo sessionInfo = _activeConnections.GetOrAdd(key, k => { - SessionInfo sessionInfo; - DeviceInfo device = null; - - if (!_activeConnections.TryGetValue(key, out sessionInfo)) - { - sessionInfo = new SessionInfo(this, _logger) - { - Client = appName, - DeviceId = deviceId, - ApplicationVersion = appVersion, - Id = key.GetMD5().ToString("N") - }; - - sessionInfo.DeviceName = deviceName; - sessionInfo.UserId = userId; - sessionInfo.UserName = username; - sessionInfo.RemoteEndPoint = remoteEndPoint; + return CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user); + }); - OnSessionStarted(sessionInfo); + sessionInfo.UserId = user == null ? Guid.Empty : user.Id; + sessionInfo.UserName = user == null ? null : user.Name; + sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); + sessionInfo.RemoteEndPoint = remoteEndPoint; + sessionInfo.Client = appName; - _activeConnections.TryAdd(key, sessionInfo); + if (!sessionInfo.HasCustomDeviceName || string.IsNullOrEmpty(sessionInfo.DeviceName)) + { + sessionInfo.DeviceName = deviceName; + } - if (!string.IsNullOrEmpty(deviceId)) - { - var userIdString = userId.HasValue ? userId.Value.ToString("N") : null; - device = _deviceManager.RegisterDevice(deviceId, deviceName, appName, appVersion, userIdString); - } - } + sessionInfo.ApplicationVersion = appVersion; - device = device ?? _deviceManager.GetDevice(deviceId); + if (user == null) + { + sessionInfo.AdditionalUsers = new SessionUserInfo[] { }; + } - if (device == null) - { - var userIdString = userId.HasValue ? userId.Value.ToString("N") : null; - device = _deviceManager.RegisterDevice(deviceId, deviceName, appName, appVersion, userIdString); - } + return sessionInfo; + } - if (device != null) - { - if (!string.IsNullOrEmpty(device.CustomName)) - { - deviceName = device.CustomName; - } - } + private SessionInfo CreateSession(string key, string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + { + var sessionInfo = new SessionInfo(this, _logger) + { + Client = appName, + DeviceId = deviceId, + ApplicationVersion = appVersion, + Id = key.GetMD5().ToString("N"), + ServerId = _appHost.SystemId + }; - sessionInfo.DeviceName = deviceName; - sessionInfo.UserId = userId; - sessionInfo.UserName = username; - sessionInfo.RemoteEndPoint = remoteEndPoint; - sessionInfo.ApplicationVersion = appVersion; + var username = user == null ? null : user.Name; - if (!userId.HasValue) - { - sessionInfo.AdditionalUsers = new SessionUserInfo[] { }; - } + sessionInfo.UserId = user == null ? Guid.Empty : user.Id; + sessionInfo.UserName = username; + sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); + sessionInfo.RemoteEndPoint = remoteEndPoint; - if (sessionInfo.SessionController == null) - { - sessionInfo.SessionController = _sessionFactories - .Select(i => i.GetSessionController(sessionInfo)) - .FirstOrDefault(i => i != null); - } + if (string.IsNullOrEmpty(deviceName)) + { + deviceName = "Network Device"; + } - return sessionInfo; + var deviceOptions = _deviceManager.GetDeviceOptions(deviceId); + if (string.IsNullOrEmpty(deviceOptions.CustomName)) + { + sessionInfo.DeviceName = deviceName; } - finally + else { - _sessionLock.Release(); + sessionInfo.DeviceName = deviceOptions.CustomName; + sessionInfo.HasCustomDeviceName = true; } + + OnSessionStarted(sessionInfo); + return sessionInfo; } private List<User> GetUsers(SessionInfo session) { var users = new List<User>(); - if (session.UserId.HasValue) + if (!session.UserId.Equals(Guid.Empty)) { - var user = _userManager.GetUserById(session.UserId.Value); + var user = _userManager.GetUserById(session.UserId); if (user == null) { @@ -498,11 +489,9 @@ namespace Emby.Server.Implementations.Session users.Add(user); - var additionalUsers = session.AdditionalUsers + users.AddRange(session.AdditionalUsers .Select(i => _userManager.GetUserById(i.UserId)) - .Where(i => i != null); - - users.AddRange(additionalUsers); + .Where(i => i != null)); } return users; @@ -546,7 +535,7 @@ namespace Emby.Server.Implementations.Session await OnPlaybackStopped(new PlaybackStopInfo { Item = session.NowPlayingItem, - ItemId = session.NowPlayingItem == null ? null : session.NowPlayingItem.Id, + ItemId = session.NowPlayingItem == null ? Guid.Empty : session.NowPlayingItem.Id, SessionId = session.Id, MediaSourceId = session.PlayState == null ? null : session.PlayState.MediaSourceId, PositionTicks = session.PlayState == null ? null : session.PlayState.PositionTicks @@ -568,12 +557,10 @@ namespace Emby.Server.Implementations.Session } } - private BaseItem GetNowPlayingItem(SessionInfo session, string itemId) + private BaseItem GetNowPlayingItem(SessionInfo session, Guid itemId) { - var idGuid = new Guid(itemId); - var item = session.FullNowPlayingItem; - if (item != null && item.Id == idGuid) + if (item != null && item.Id.Equals(itemId)) { return item; } @@ -593,6 +580,8 @@ namespace Emby.Server.Implementations.Session /// <exception cref="System.ArgumentNullException">info</exception> public async Task OnPlaybackStart(PlaybackStartInfo info) { + CheckDisposed(); + if (info == null) { throw new ArgumentNullException("info"); @@ -600,7 +589,7 @@ namespace Emby.Server.Implementations.Session var session = GetSession(info.SessionId); - var libraryItem = string.IsNullOrWhiteSpace(info.ItemId) + var libraryItem = info.ItemId.Equals(Guid.Empty) ? null : GetNowPlayingItem(session, info.ItemId); @@ -619,7 +608,7 @@ namespace Emby.Server.Implementations.Session { foreach (var user in users) { - OnPlaybackStart(user.Id, libraryItem); + OnPlaybackStart(user, libraryItem); } } @@ -633,12 +622,11 @@ namespace Emby.Server.Implementations.Session MediaInfo = info.Item, DeviceName = session.DeviceName, ClientName = session.Client, - DeviceId = session.DeviceId + DeviceId = session.DeviceId, + Session = session }, _logger); - await SendPlaybackStartNotification(session, CancellationToken.None).ConfigureAwait(false); - StartIdleCheckTimer(); } @@ -647,9 +635,9 @@ namespace Emby.Server.Implementations.Session /// </summary> /// <param name="userId">The user identifier.</param> /// <param name="item">The item.</param> - private void OnPlaybackStart(Guid userId, IHasUserData item) + private void OnPlaybackStart(User user, BaseItem item) { - var data = _userDataManager.GetUserData(userId, item); + var data = _userDataManager.GetUserData(user, item); data.PlayCount++; data.LastPlayedDate = DateTime.UtcNow; @@ -666,7 +654,7 @@ namespace Emby.Server.Implementations.Session data.Played = false; } - _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None); + _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None); } public Task OnPlaybackProgress(PlaybackProgressInfo info) @@ -679,6 +667,8 @@ namespace Emby.Server.Implementations.Session /// </summary> public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated) { + CheckDisposed(); + if (info == null) { throw new ArgumentNullException("info"); @@ -686,7 +676,7 @@ namespace Emby.Server.Implementations.Session var session = GetSession(info.SessionId); - var libraryItem = string.IsNullOrWhiteSpace(info.ItemId) + var libraryItem = info.ItemId.Equals(Guid.Empty) ? null : GetNowPlayingItem(session, info.ItemId); @@ -694,7 +684,8 @@ namespace Emby.Server.Implementations.Session var users = GetUsers(session); - if (libraryItem != null) + // only update saved user data on actual check-ins, not automated ones + if (libraryItem != null && !isAutomated) { foreach (var user in users) { @@ -714,7 +705,8 @@ namespace Emby.Server.Implementations.Session DeviceId = session.DeviceId, IsPaused = info.IsPaused, PlaySessionId = info.PlaySessionId, - IsAutomated = isAutomated + IsAutomated = isAutomated, + Session = session }, _logger); @@ -728,39 +720,70 @@ namespace Emby.Server.Implementations.Session private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info) { - var data = _userDataManager.GetUserData(user.Id, item); + var data = _userDataManager.GetUserData(user, item); var positionTicks = info.PositionTicks; + var changed = false; + if (positionTicks.HasValue) { _userDataManager.UpdatePlayState(item, data, positionTicks.Value); + changed = true; + } - UpdatePlaybackSettings(user, info, data); + var tracksChanged = UpdatePlaybackSettings(user, info, data); + if (!tracksChanged) + { + changed = true; + } - _userDataManager.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None); + if (changed) + { + _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None); } + } - private void UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data) + private bool UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data) { + var changed = false; + if (user.Configuration.RememberAudioSelections) { - data.AudioStreamIndex = info.AudioStreamIndex; + if (data.AudioStreamIndex != info.AudioStreamIndex) + { + data.AudioStreamIndex = info.AudioStreamIndex; + changed = true; + } } else { - data.AudioStreamIndex = null; + if (data.AudioStreamIndex.HasValue) + { + data.AudioStreamIndex = null; + changed = true; + } } if (user.Configuration.RememberSubtitleSelections) { - data.SubtitleStreamIndex = info.SubtitleStreamIndex; + if (data.SubtitleStreamIndex != info.SubtitleStreamIndex) + { + data.SubtitleStreamIndex = info.SubtitleStreamIndex; + changed = true; + } } else { - data.SubtitleStreamIndex = null; + if (data.SubtitleStreamIndex.HasValue) + { + data.SubtitleStreamIndex = null; + changed = true; + } } + + return changed; } /// <summary> @@ -772,6 +795,8 @@ namespace Emby.Server.Implementations.Session /// <exception cref="System.ArgumentOutOfRangeException">positionTicks</exception> public async Task OnPlaybackStopped(PlaybackStopInfo info) { + CheckDisposed(); + if (info == null) { throw new ArgumentNullException("info"); @@ -786,28 +811,28 @@ namespace Emby.Server.Implementations.Session session.StopAutomaticProgress(); - var libraryItem = string.IsNullOrWhiteSpace(info.ItemId) + var libraryItem = info.ItemId.Equals(Guid.Empty) ? null : GetNowPlayingItem(session, info.ItemId); // Normalize - if (string.IsNullOrWhiteSpace(info.MediaSourceId)) + if (string.IsNullOrEmpty(info.MediaSourceId)) { - info.MediaSourceId = info.ItemId; + info.MediaSourceId = info.ItemId.ToString("N"); } - if (!string.IsNullOrWhiteSpace(info.ItemId) && info.Item == null && libraryItem != null) + if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null) { var current = session.NowPlayingItem; - if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase)) + if (current == null || !info.ItemId.Equals(current.Id)) { MediaSourceInfo mediaSource = null; var hasMediaSources = libraryItem as IHasMediaSources; if (hasMediaSources != null) { - mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); + mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); } info.Item = GetItemInfo(libraryItem, mediaSource); @@ -829,6 +854,13 @@ namespace Emby.Server.Implementations.Session msString); } + if (info.NowPlayingQueue != null) + { + session.NowPlayingQueue = info.NowPlayingQueue; + } + + session.PlaylistItemId = info.PlaylistItemId; + RemoveNowPlayingItem(session); var users = GetUsers(session); @@ -838,11 +870,11 @@ namespace Emby.Server.Implementations.Session { foreach (var user in users) { - playedToCompletion = OnPlaybackStopped(user.Id, libraryItem, info.PositionTicks, info.Failed); + playedToCompletion = OnPlaybackStopped(user, libraryItem, info.PositionTicks, info.Failed); } } - if (!string.IsNullOrWhiteSpace(info.LiveStreamId)) + if (!string.IsNullOrEmpty(info.LiveStreamId)) { try { @@ -864,20 +896,19 @@ namespace Emby.Server.Implementations.Session MediaInfo = info.Item, DeviceName = session.DeviceName, ClientName = session.Client, - DeviceId = session.DeviceId + DeviceId = session.DeviceId, + Session = session }, _logger); - - await SendPlaybackStoppedNotification(session, CancellationToken.None).ConfigureAwait(false); } - private bool OnPlaybackStopped(Guid userId, BaseItem item, long? positionTicks, bool playbackFailed) + private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed) { bool playedToCompletion = false; if (!playbackFailed) { - var data = _userDataManager.GetUserData(userId, item); + var data = _userDataManager.GetUserData(user, item); if (positionTicks.HasValue) { @@ -892,7 +923,7 @@ namespace Emby.Server.Implementations.Session playedToCompletion = true; } - _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None); + _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None); } return playedToCompletion; @@ -932,6 +963,8 @@ namespace Emby.Server.Implementations.Session public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken) { + CheckDisposed(); + var generalCommand = new GeneralCommand { Name = GeneralCommandType.DisplayMessage.ToString() @@ -950,22 +983,37 @@ namespace Emby.Server.Implementations.Session public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken) { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); - if (!string.IsNullOrWhiteSpace(controllingSessionId)) + if (!string.IsNullOrEmpty(controllingSessionId)) { var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); } - return session.SessionController.SendGeneralCommand(command, cancellationToken); + return SendMessageToSession(session, "GeneralCommand", command, cancellationToken); + } + + private async Task SendMessageToSession<T>(SessionInfo session, string name, T data, CancellationToken cancellationToken) + { + var controllers = session.SessionControllers.ToArray(); + var messageId = Guid.NewGuid().ToString("N"); + + foreach (var controller in controllers) + { + await controller.SendMessage(name, messageId, data, controllers, cancellationToken).ConfigureAwait(false); + } } public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken) { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); - var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null; + var user = !session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(session.UserId) : null; List<BaseItem> items; @@ -994,7 +1042,7 @@ namespace Emby.Server.Implementations.Session command.PlayCommand = PlayCommand.PlayNow; } - command.ItemIds = items.Select(i => i.Id.ToString("N")).ToArray(items.Count); + command.ItemIds = items.Select(i => i.Id).ToArray(items.Count); if (user != null) { @@ -1004,11 +1052,6 @@ namespace Emby.Server.Implementations.Session } } - if (items.Any(i => !session.PlayableMediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format("{0} is unable to play the requested media type.", session.DeviceName ?? session.Id)); - } - if (user != null && command.ItemIds.Length == 1 && user.Configuration.EnableNextEpisodeAutoPlay) { var episode = _libraryManager.GetItemById(command.ItemIds[0]) as Episode; @@ -1027,26 +1070,26 @@ namespace Emby.Server.Implementations.Session if (episodes.Count > 0) { - command.ItemIds = episodes.Select(i => i.Id.ToString("N")).ToArray(episodes.Count); + command.ItemIds = episodes.Select(i => i.Id).ToArray(episodes.Count); } } } } - if (!string.IsNullOrWhiteSpace(controllingSessionId)) + if (!string.IsNullOrEmpty(controllingSessionId)) { var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - if (controllingSession.UserId.HasValue) + if (!controllingSession.UserId.Equals(Guid.Empty)) { - command.ControllingUserId = controllingSession.UserId.Value.ToString("N"); + command.ControllingUserId = controllingSession.UserId; } } - await session.SessionController.SendPlayCommand(command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } - private List<BaseItem> TranslateItemForPlayback(string id, User user) + private IList<BaseItem> TranslateItemForPlayback(Guid id, User user) { var item = _libraryManager.GetItemById(id); @@ -1060,7 +1103,7 @@ namespace Emby.Server.Implementations.Session if (byName != null) { - var items = byName.GetTaggedItems(new InternalItemsQuery(user) + return byName.GetTaggedItems(new InternalItemsQuery(user) { IsFolder = false, Recursive = true, @@ -1072,19 +1115,16 @@ namespace Emby.Server.Implementations.Session ItemFields.SortName } }, - IsVirtualItem = false + IsVirtualItem = false, + OrderBy = new ValueTuple<string, SortOrder>[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) } }); - - return FilterToSingleMediaType(items) - .OrderBy(i => i.SortName) - .ToList(); } if (item.IsFolder) { var folder = (Folder)item; - var itemsResult = folder.GetItemList(new InternalItemsQuery(user) + return folder.GetItemList(new InternalItemsQuery(user) { Recursive = true, IsFolder = false, @@ -1096,28 +1136,16 @@ namespace Emby.Server.Implementations.Session ItemFields.SortName } }, - IsVirtualItem = false + IsVirtualItem = false, + OrderBy = new ValueTuple<string, SortOrder>[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) } }); - - return FilterToSingleMediaType(itemsResult) - .OrderBy(i => i.SortName) - .ToList(); } return new List<BaseItem> { item }; } - private IEnumerable<BaseItem> FilterToSingleMediaType(IEnumerable<BaseItem> items) - { - return items - .Where(i => !string.IsNullOrWhiteSpace(i.MediaType)) - .ToLookup(i => i.MediaType, StringComparer.OrdinalIgnoreCase) - .OrderByDescending(i => i.Count()) - .FirstOrDefault(); - } - - private IEnumerable<BaseItem> TranslateItemForInstantMix(string id, User user) + private IEnumerable<BaseItem> TranslateItemForInstantMix(Guid id, User user) { var item = _libraryManager.GetItemById(id); @@ -1146,19 +1174,21 @@ namespace Emby.Server.Implementations.Session public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken) { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); - if (!string.IsNullOrWhiteSpace(controllingSessionId)) + if (!string.IsNullOrEmpty(controllingSessionId)) { var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); - if (controllingSession.UserId.HasValue) + if (!controllingSession.UserId.Equals(Guid.Empty)) { - command.ControllingUserId = controllingSession.UserId.Value.ToString("N"); + command.ControllingUserId = controllingSession.UserId.ToString("N"); } } - return session.SessionController.SendPlaystateCommand(command, cancellationToken); + return SendMessageToSession(session, "Playstate", command, cancellationToken); } private void AssertCanControl(SessionInfo session, SessionInfo controllingSession) @@ -1180,20 +1210,22 @@ namespace Emby.Server.Implementations.Session /// <returns>Task.</returns> public async Task SendRestartRequiredNotification(CancellationToken cancellationToken) { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); + CheckDisposed(); + + var sessions = Sessions.ToList(); var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendRestartRequiredNotification(cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "RestartRequired", string.Empty, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error in SendRestartRequiredNotification.", ex); } - }, cancellationToken)); + }, cancellationToken)).ToArray(); await Task.WhenAll(tasks).ConfigureAwait(false); } @@ -1205,20 +1237,22 @@ namespace Emby.Server.Implementations.Session /// <returns>Task.</returns> public Task SendServerShutdownNotification(CancellationToken cancellationToken) { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); + CheckDisposed(); + + var sessions = Sessions.ToList(); var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendServerShutdownNotification(cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "ServerShuttingDown", string.Empty, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error in SendServerShutdownNotification.", ex); } - }, cancellationToken)); + }, cancellationToken)).ToArray(); return Task.WhenAll(tasks); } @@ -1230,85 +1264,24 @@ namespace Emby.Server.Implementations.Session /// <returns>Task.</returns> public Task SendServerRestartNotification(CancellationToken cancellationToken) { + CheckDisposed(); + _logger.Debug("Beginning SendServerRestartNotification"); - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); + var sessions = Sessions.ToList(); var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendServerRestartNotification(cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "ServerRestarting", string.Empty, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error in SendServerRestartNotification.", ex); } - }, cancellationToken)); - - return Task.WhenAll(tasks); - } - - public Task SendSessionEndedNotification(SessionInfo sessionInfo, CancellationToken cancellationToken) - { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); - var dto = GetSessionInfoDto(sessionInfo); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await session.SessionController.SendSessionEndedNotification(dto, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in SendSessionEndedNotification.", ex); - } - - }, cancellationToken)); - - return Task.WhenAll(tasks); - } - - public Task SendPlaybackStartNotification(SessionInfo sessionInfo, CancellationToken cancellationToken) - { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); - var dto = GetSessionInfoDto(sessionInfo); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await session.SessionController.SendPlaybackStartNotification(dto, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in SendPlaybackStartNotification.", ex); - } - - }, cancellationToken)); - - return Task.WhenAll(tasks); - } - - public Task SendPlaybackStoppedNotification(SessionInfo sessionInfo, CancellationToken cancellationToken) - { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList(); - var dto = GetSessionInfoDto(sessionInfo); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await session.SessionController.SendPlaybackStoppedNotification(dto, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in SendPlaybackStoppedNotification.", ex); - } - - }, cancellationToken)); + }, cancellationToken)).ToArray(); return Task.WhenAll(tasks); } @@ -1320,16 +1293,18 @@ namespace Emby.Server.Implementations.Session /// <param name="userId">The user identifier.</param> /// <exception cref="System.UnauthorizedAccessException">Cannot modify additional users without authenticating first.</exception> /// <exception cref="System.ArgumentException">The requested user is already the primary user of the session.</exception> - public void AddAdditionalUser(string sessionId, string userId) + public void AddAdditionalUser(string sessionId, Guid userId) { + CheckDisposed(); + var session = GetSession(sessionId); - if (session.UserId.HasValue && session.UserId.Value == new Guid(userId)) + if (session.UserId.Equals(userId)) { throw new ArgumentException("The requested user is already the primary user of the session."); } - if (session.AdditionalUsers.All(i => new Guid(i.UserId) != new Guid(userId))) + if (session.AdditionalUsers.All(i => !i.UserId.Equals(userId))) { var user = _userManager.GetUserById(userId); @@ -1352,16 +1327,18 @@ namespace Emby.Server.Implementations.Session /// <param name="userId">The user identifier.</param> /// <exception cref="System.UnauthorizedAccessException">Cannot modify additional users without authenticating first.</exception> /// <exception cref="System.ArgumentException">The requested user is already the primary user of the session.</exception> - public void RemoveAdditionalUser(string sessionId, string userId) + public void RemoveAdditionalUser(string sessionId, Guid userId) { + CheckDisposed(); + var session = GetSession(sessionId); - if (session.UserId.HasValue && session.UserId.Value == new Guid(userId)) + if (session.UserId.Equals(userId)) { throw new ArgumentException("The requested user is already the primary user of the session."); } - var user = session.AdditionalUsers.FirstOrDefault(i => new Guid(i.UserId) == new Guid(userId)); + var user = session.AdditionalUsers.FirstOrDefault(i => i.UserId.Equals(userId)); if (user != null) { @@ -1389,12 +1366,13 @@ namespace Emby.Server.Implementations.Session private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword) { + CheckDisposed(); + User user = null; - if (!string.IsNullOrWhiteSpace(request.UserId)) + if (!request.UserId.Equals(Guid.Empty)) { - var idGuid = new Guid(request.UserId); user = _userManager.Users - .FirstOrDefault(i => i.Id == idGuid); + .FirstOrDefault(i => i.Id == request.UserId); } if (user == null) @@ -1405,14 +1383,10 @@ namespace Emby.Server.Implementations.Session if (user != null) { - if (!user.IsParentalScheduleAllowed()) - { - throw new SecurityException("User is not allowed access at this time."); - } - - if (!string.IsNullOrWhiteSpace(request.DeviceId)) + // TODO: Move this to userManager? + if (!string.IsNullOrEmpty(request.DeviceId)) { - if (!_deviceManager.CanAccessDevice(user.Id.ToString("N"), request.DeviceId)) + if (!_deviceManager.CanAccessDevice(user, request.DeviceId)) { throw new SecurityException("User is not allowed access from this device."); } @@ -1421,7 +1395,7 @@ namespace Emby.Server.Implementations.Session if (enforcePassword) { - var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint, true).ConfigureAwait(false); + var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.RemoteEndPoint, true).ConfigureAwait(false); if (result == null) { @@ -1433,72 +1407,95 @@ namespace Emby.Server.Implementations.Session user = result; } - var token = GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.AppVersion, request.DeviceName); + var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); - EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs<AuthenticationRequest>(request), _logger); - - var session = await LogSessionActivity(request.App, + var session = LogSessionActivity(request.App, request.AppVersion, request.DeviceId, request.DeviceName, request.RemoteEndPoint, - user) - .ConfigureAwait(false); + user); - return new AuthenticationResult + var returnResult = new AuthenticationResult { User = _userManager.GetUserDto(user, request.RemoteEndPoint), - SessionInfo = GetSessionInfoDto(session), + SessionInfo = session, AccessToken = token, ServerId = _appHost.SystemId }; - } + EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs<AuthenticationResult>(returnResult), _logger); + + return returnResult; + } - private string GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName) + private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) { var existing = _authRepo.Get(new AuthenticationInfoQuery { DeviceId = deviceId, - IsActive = true, - UserId = userId, + UserId = user.Id, Limit = 1 - }); - if (existing.Items.Length > 0) + }).Items.FirstOrDefault(); + + var allExistingForDevice = _authRepo.Get(new AuthenticationInfoQuery + { + DeviceId = deviceId + + }).Items; + + foreach (var auth in allExistingForDevice) + { + if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal)) + { + try + { + Logout(auth); + } + catch + { + + } + } + } + + if (existing != null) { - var token = existing.Items[0].AccessToken; - _logger.Info("Reissuing access token: " + token); - return token; + _logger.Info("Reissuing access token: " + existing.AccessToken); + return existing.AccessToken; } + var now = DateTime.UtcNow; + var newToken = new AuthenticationInfo { AppName = app, AppVersion = appVersion, - DateCreated = DateTime.UtcNow, + DateCreated = now, + DateLastActivity = now, DeviceId = deviceId, DeviceName = deviceName, - UserId = userId, - IsActive = true, - AccessToken = Guid.NewGuid().ToString("N") + UserId = user.Id, + AccessToken = Guid.NewGuid().ToString("N"), + UserName = user.Name }; - _logger.Info("Creating new access token for user {0}", userId); - _authRepo.Create(newToken, CancellationToken.None); + _logger.Info("Creating new access token for user {0}", user.Id); + _authRepo.Create(newToken); return newToken.AccessToken; } public void Logout(string accessToken) { - if (string.IsNullOrWhiteSpace(accessToken)) + CheckDisposed(); + + if (string.IsNullOrEmpty(accessToken)) { throw new ArgumentNullException("accessToken"); } - _logger.Info("Logging out access token {0}", accessToken); - var existing = _authRepo.Get(new AuthenticationInfoQuery { Limit = 1, @@ -1508,33 +1505,41 @@ namespace Emby.Server.Implementations.Session if (existing != null) { - existing.IsActive = false; + Logout(existing); + } + } - _authRepo.Update(existing, CancellationToken.None); + public void Logout(AuthenticationInfo existing) + { + CheckDisposed(); - var sessions = Sessions - .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase)) - .ToList(); + _logger.Info("Logging out access token {0}", existing.AccessToken); + + _authRepo.Delete(existing); - foreach (var session in sessions) + var sessions = Sessions + .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var session in sessions) + { + try { - try - { - ReportSessionEnded(session.Id); - } - catch (Exception ex) - { - _logger.ErrorException("Error reporting session ended", ex); - } + ReportSessionEnded(session.Id); + } + catch (Exception ex) + { + _logger.ErrorException("Error reporting session ended", ex); } } } - public void RevokeUserTokens(string userId, string currentAccessToken) + public void RevokeUserTokens(Guid userId, string currentAccessToken) { + CheckDisposed(); + var existing = _authRepo.Get(new AuthenticationInfoQuery { - IsActive = true, UserId = userId }); @@ -1542,7 +1547,7 @@ namespace Emby.Server.Implementations.Session { if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase)) { - Logout(info.AccessToken); + Logout(info); } } } @@ -1559,6 +1564,8 @@ namespace Emby.Server.Implementations.Session /// <param name="capabilities">The capabilities.</param> public void ReportCapabilities(string sessionId, ClientCapabilities capabilities) { + CheckDisposed(); + var session = GetSession(sessionId); ReportCapabilities(session, capabilities, true); @@ -1570,24 +1577,22 @@ namespace Emby.Server.Implementations.Session { session.Capabilities = capabilities; - if (!string.IsNullOrWhiteSpace(capabilities.MessageCallbackUrl)) + if (!string.IsNullOrEmpty(capabilities.PushToken)) { - var controller = session.SessionController as HttpSessionController; - - if (controller == null) + if (string.Equals(capabilities.PushTokenType, "firebase", StringComparison.OrdinalIgnoreCase) && FirebaseSessionController.IsSupported(_appHost)) { - session.SessionController = new HttpSessionController(_httpClient, _jsonSerializer, session, capabilities.MessageCallbackUrl, this); + EnsureFirebaseController(session, capabilities.PushToken); } } - EventHelper.FireEventIfNotNull(CapabilitiesChanged, this, new SessionEventArgs + if (saveCapabilities) { - SessionInfo = session + EventHelper.FireEventIfNotNull(CapabilitiesChanged, this, new SessionEventArgs + { + SessionInfo = session - }, _logger); + }, _logger); - if (saveCapabilities) - { try { SaveCapabilities(session.DeviceId, capabilities); @@ -1599,6 +1604,11 @@ namespace Emby.Server.Implementations.Session } } + private void EnsureFirebaseController(SessionInfo session, string token) + { + session.EnsureController<FirebaseSessionController>(s => new FirebaseSessionController(_httpClient, _appHost, _jsonSerializer, s, token, this)); + } + private ClientCapabilities GetSavedCapabilities(string deviceId) { return _deviceManager.GetCapabilities(deviceId); @@ -1609,45 +1619,6 @@ namespace Emby.Server.Implementations.Session _deviceManager.SaveCapabilities(deviceId, capabilities); } - public SessionInfoDto GetSessionInfoDto(SessionInfo session) - { - var dto = new SessionInfoDto - { - Client = session.Client, - DeviceId = session.DeviceId, - DeviceName = session.DeviceName, - Id = session.Id, - LastActivityDate = session.LastActivityDate, - NowViewingItem = session.NowViewingItem, - ApplicationVersion = session.ApplicationVersion, - PlayableMediaTypes = session.PlayableMediaTypes, - AdditionalUsers = session.AdditionalUsers, - SupportedCommands = session.SupportedCommands, - UserName = session.UserName, - NowPlayingItem = session.NowPlayingItem, - SupportsRemoteControl = session.SupportsMediaControl, - PlayState = session.PlayState, - AppIconUrl = session.AppIconUrl, - TranscodingInfo = session.NowPlayingItem == null ? null : session.TranscodingInfo - }; - - dto.ServerId = _appHost.SystemId; - - if (session.UserId.HasValue) - { - dto.UserId = session.UserId.Value.ToString("N"); - - var user = _userManager.GetUserById(session.UserId.Value); - - if (user != null) - { - dto.UserPrimaryImageTag = GetImageCacheTag(user, ImageType.Primary); - } - } - - return dto; - } - private DtoOptions _itemInfoDtoOptions; /// <summary> @@ -1672,7 +1643,6 @@ namespace Emby.Server.Implementations.Session var fields = dtoOptions.Fields.ToList(); fields.Remove(ItemFields.BasicSyncInfo); - fields.Remove(ItemFields.SyncInfo); fields.Remove(ItemFields.CanDelete); fields.Remove(ItemFields.CanDownload); fields.Remove(ItemFields.ChildCount); @@ -1682,7 +1652,6 @@ namespace Emby.Server.Implementations.Session fields.Remove(ItemFields.DateLastSaved); fields.Remove(ItemFields.DisplayPreferencesId); fields.Remove(ItemFields.Etag); - fields.Remove(ItemFields.ExternalEtag); fields.Remove(ItemFields.InheritedParentalRatingValue); fields.Remove(ItemFields.ItemCounts); fields.Remove(ItemFields.MediaSourceCount); @@ -1698,8 +1667,7 @@ namespace Emby.Server.Implementations.Session fields.Remove(ItemFields.Settings); fields.Remove(ItemFields.SortName); fields.Remove(ItemFields.Tags); - fields.Remove(ItemFields.ThemeSongIds); - fields.Remove(ItemFields.ThemeVideoIds); + fields.Remove(ItemFields.ExtraIds); dtoOptions.Fields = fields.ToArray(fields.Count); @@ -1729,14 +1697,9 @@ namespace Emby.Server.Implementations.Session } } - private string GetDtoId(BaseItem item) - { - return _dtoService.GetDtoId(item); - } - public void ReportNowViewingItem(string sessionId, string itemId) { - if (string.IsNullOrWhiteSpace(itemId)) + if (string.IsNullOrEmpty(itemId)) { throw new ArgumentNullException("itemId"); } @@ -1776,46 +1739,31 @@ namespace Emby.Server.Implementations.Session string.Equals(i.Client, client)); } - public Task<SessionInfo> GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) + public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) { if (info == null) { throw new ArgumentNullException("info"); } - var user = string.IsNullOrWhiteSpace(info.UserId) + var user = info.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(info.UserId); - appVersion = string.IsNullOrWhiteSpace(appVersion) + appVersion = string.IsNullOrEmpty(appVersion) ? info.AppVersion : appVersion; var deviceName = info.DeviceName; var appName = info.AppName; - if (!string.IsNullOrWhiteSpace(deviceId)) - { - // Replace the info from the token with more recent info - var device = _deviceManager.GetDevice(deviceId); - if (device != null) - { - deviceName = device.Name; - appName = device.AppName; - - if (!string.IsNullOrWhiteSpace(device.AppVersion)) - { - appVersion = device.AppVersion; - } - } - } - else + if (string.IsNullOrEmpty(deviceId)) { deviceId = info.DeviceId; } // Prevent argument exception - if (string.IsNullOrWhiteSpace(appVersion)) + if (string.IsNullOrEmpty(appVersion)) { appVersion = "1"; } @@ -1823,7 +1771,7 @@ namespace Emby.Server.Implementations.Session return LogSessionActivity(appName, appVersion, deviceId, deviceName, remoteEndpoint, user); } - public Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) + public SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) { var result = _authRepo.Get(new AuthenticationInfoQuery { @@ -1834,7 +1782,7 @@ namespace Emby.Server.Implementations.Session if (info == null) { - return Task.FromResult<SessionInfo>(null); + return null; } return GetSessionByAuthenticationToken(info, deviceId, remoteEndpoint, null); @@ -1842,51 +1790,115 @@ namespace Emby.Server.Implementations.Session public Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken) { - var adminUserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id.ToString("N")).ToList(); + CheckDisposed(); + + var adminUserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToList(); return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken); } - public Task SendMessageToUserSessions<T>(List<string> userIds, string name, T data, - CancellationToken cancellationToken) + public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, Func<T> dataFn, CancellationToken cancellationToken) + { + CheckDisposed(); + + var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)).ToList(); + + if (sessions.Count == 0) + { + return Task.CompletedTask; + } + + var data = dataFn(); + + var tasks = sessions.Select(session => Task.Run(async () => + { + try + { + await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending message", ex); + } + + }, cancellationToken)).ToArray(); + + return Task.WhenAll(tasks); + } + + public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken) + { + CheckDisposed(); + + var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)).ToList(); + + var tasks = sessions.Select(session => Task.Run(async () => + { + try + { + await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending message", ex); + } + + }, cancellationToken)).ToArray(); + + return Task.WhenAll(tasks); + } + + public Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken) { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null && userIds.Any(i.ContainsUser)).ToList(); + CheckDisposed(); + + var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)).ToList(); var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendMessage(name, data, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error sending message", ex); } - }, cancellationToken)); + }, cancellationToken)).ToArray(); return Task.WhenAll(tasks); } - public Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, - CancellationToken cancellationToken) + public Task SendMessageToUserDeviceAndAdminSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken) { - var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null && string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)).ToList(); + CheckDisposed(); + + var sessions = Sessions + .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)) + .ToList(); var tasks = sessions.Select(session => Task.Run(async () => { try { - await session.SessionController.SendMessage(name, data, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error sending message", ex); } - }, cancellationToken)); + }, cancellationToken)).ToArray(); return Task.WhenAll(tasks); } + + private bool IsAdminSession(SessionInfo s) + { + var user = _userManager.GetUserById(s.UserId); + + return user != null && user.Policy.IsAdministrator; + } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a5af843db..9ab4753fb 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -19,11 +19,6 @@ namespace Emby.Server.Implementations.Session public class SessionWebSocketListener : IWebSocketListener, IDisposable { /// <summary> - /// The _true task result - /// </summary> - private readonly Task _trueTaskResult = Task.FromResult(true); - - /// <summary> /// The _session manager /// </summary> private readonly ISessionManager _sessionManager; @@ -39,7 +34,6 @@ namespace Emby.Server.Implementations.Session private readonly IJsonSerializer _json; private readonly IHttpServer _httpServer; - private readonly IServerManager _serverManager; /// <summary> @@ -50,32 +44,22 @@ namespace Emby.Server.Implementations.Session /// <param name="json">The json.</param> /// <param name="httpServer">The HTTP server.</param> /// <param name="serverManager">The server manager.</param> - public SessionWebSocketListener(ISessionManager sessionManager, ILogManager logManager, IJsonSerializer json, IHttpServer httpServer, IServerManager serverManager) + public SessionWebSocketListener(ISessionManager sessionManager, ILogManager logManager, IJsonSerializer json, IHttpServer httpServer) { _sessionManager = sessionManager; _logger = logManager.GetLogger(GetType().Name); _json = json; _httpServer = httpServer; - _serverManager = serverManager; - serverManager.WebSocketConnected += _serverManager_WebSocketConnected; + httpServer.WebSocketConnected += _serverManager_WebSocketConnected; } - async void _serverManager_WebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e) + void _serverManager_WebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e) { - var session = await GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint).ConfigureAwait(false); + var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint); if (session != null) { - var controller = session.SessionController as WebSocketController; - - if (controller == null) - { - controller = new WebSocketController(session, _logger, _sessionManager); - } - - controller.AddWebSocket(e.Argument); - - session.SessionController = controller; + EnsureController(session, e.Argument); } else { @@ -83,7 +67,7 @@ namespace Emby.Server.Implementations.Session } } - private Task<SessionInfo> GetSession(QueryParamCollection queryString, string remoteEndpoint) + private SessionInfo GetSession(QueryParamCollection queryString, string remoteEndpoint) { if (queryString == null) { @@ -93,7 +77,7 @@ namespace Emby.Server.Implementations.Session var token = queryString["api_key"]; if (string.IsNullOrWhiteSpace(token)) { - return Task.FromResult<SessionInfo>(null); + return null; } var deviceId = queryString["deviceId"]; return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); @@ -101,8 +85,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { - _serverManager.WebSocketConnected -= _serverManager_WebSocketConnected; - GC.SuppressFinalize(this); + _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; } /// <summary> @@ -112,350 +95,15 @@ namespace Emby.Server.Implementations.Session /// <returns>Task.</returns> public Task ProcessMessage(WebSocketMessageInfo message) { - if (string.Equals(message.MessageType, "Identity", StringComparison.OrdinalIgnoreCase)) - { - ProcessIdentityMessage(message); - } - else if (string.Equals(message.MessageType, "Context", StringComparison.OrdinalIgnoreCase)) - { - ProcessContextMessage(message); - } - else if (string.Equals(message.MessageType, "PlaybackStart", StringComparison.OrdinalIgnoreCase)) - { - OnPlaybackStart(message); - } - else if (string.Equals(message.MessageType, "PlaybackProgress", StringComparison.OrdinalIgnoreCase)) - { - OnPlaybackProgress(message); - } - else if (string.Equals(message.MessageType, "PlaybackStopped", StringComparison.OrdinalIgnoreCase)) - { - OnPlaybackStopped(message); - } - else if (string.Equals(message.MessageType, "ReportPlaybackStart", StringComparison.OrdinalIgnoreCase)) - { - ReportPlaybackStart(message); - } - else if (string.Equals(message.MessageType, "ReportPlaybackProgress", StringComparison.OrdinalIgnoreCase)) - { - ReportPlaybackProgress(message); - } - else if (string.Equals(message.MessageType, "ReportPlaybackStopped", StringComparison.OrdinalIgnoreCase)) - { - ReportPlaybackStopped(message); - } - - return _trueTaskResult; - } - - /// <summary> - /// Processes the identity message. - /// </summary> - /// <param name="message">The message.</param> - private async void ProcessIdentityMessage(WebSocketMessageInfo message) - { - _logger.Debug("Received Identity message: " + message.Data); - - var vals = message.Data.Split('|'); - - if (vals.Length < 3) - { - _logger.Error("Client sent invalid identity message."); - return; - } - - var client = vals[0]; - var deviceId = vals[1]; - var version = vals[2]; - var deviceName = vals.Length > 3 ? vals[3] : string.Empty; - - var session = _sessionManager.GetSession(deviceId, client, version); - - if (session == null && !string.IsNullOrEmpty(deviceName)) - { - _logger.Debug("Logging session activity"); - - session = await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, message.Connection.RemoteEndPoint, null).ConfigureAwait(false); - } - - if (session != null) - { - var controller = session.SessionController as WebSocketController; - - if (controller == null) - { - controller = new WebSocketController(session, _logger, _sessionManager); - } - - controller.AddWebSocket(message.Connection); - - session.SessionController = controller; - } - else - { - _logger.Warn("Unable to determine session based on identity message: {0}", message.Data); - } - } - - /// <summary> - /// Processes the context message. - /// </summary> - /// <param name="message">The message.</param> - private void ProcessContextMessage(WebSocketMessageInfo message) - { - var session = GetSessionFromMessage(message); - - if (session != null) - { - var vals = message.Data.Split('|'); - - var itemId = vals[1]; - - if (!string.IsNullOrWhiteSpace(itemId)) - { - _sessionManager.ReportNowViewingItem(session.Id, itemId); - } - } - } - - /// <summary> - /// Gets the session from message. - /// </summary> - /// <param name="message">The message.</param> - /// <returns>SessionInfo.</returns> - private SessionInfo GetSessionFromMessage(WebSocketMessageInfo message) - { - var result = _sessionManager.Sessions.FirstOrDefault(i => - { - var controller = i.SessionController as WebSocketController; - - if (controller != null) - { - if (controller.Sockets.Any(s => s.Id == message.Connection.Id)) - { - return true; - } - } - - return false; - - }); - - if (result == null) - { - _logger.Error("Unable to find session based on web socket message"); - } - - return result; - } - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - /// <summary> - /// Reports the playback start. - /// </summary> - /// <param name="message">The message.</param> - private void OnPlaybackStart(WebSocketMessageInfo message) - { - _logger.Debug("Received PlaybackStart message"); - - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var vals = message.Data.Split('|'); - - var itemId = vals[0]; - - var canSeek = true; - - if (vals.Length > 1) - { - canSeek = string.Equals(vals[1], "true", StringComparison.OrdinalIgnoreCase); - } - if (vals.Length > 2) - { - // vals[2] used to be QueueableMediaTypes - } - - var info = new PlaybackStartInfo - { - CanSeek = canSeek, - ItemId = itemId, - SessionId = session.Id - }; - - if (vals.Length > 3) - { - info.MediaSourceId = vals[3]; - } - - if (vals.Length > 4 && !string.IsNullOrWhiteSpace(vals[4])) - { - info.AudioStreamIndex = int.Parse(vals[4], _usCulture); - } - - if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[5])) - { - info.SubtitleStreamIndex = int.Parse(vals[5], _usCulture); - } - - _sessionManager.OnPlaybackStart(info); - } + return Task.CompletedTask; } - private void ReportPlaybackStart(WebSocketMessageInfo message) + private void EnsureController(SessionInfo session, IWebSocketConnection connection) { - _logger.Debug("Received ReportPlaybackStart message"); - - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var info = _json.DeserializeFromString<PlaybackStartInfo>(message.Data); + var controllerInfo = session.EnsureController<WebSocketController>(s => new WebSocketController(s, _logger, _sessionManager)); - info.SessionId = session.Id; - - _sessionManager.OnPlaybackStart(info); - } - } - - private void ReportPlaybackProgress(WebSocketMessageInfo message) - { - //_logger.Debug("Received ReportPlaybackProgress message"); - - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var info = _json.DeserializeFromString<PlaybackProgressInfo>(message.Data); - - info.SessionId = session.Id; - - _sessionManager.OnPlaybackProgress(info); - } - } - - /// <summary> - /// Reports the playback progress. - /// </summary> - /// <param name="message">The message.</param> - private void OnPlaybackProgress(WebSocketMessageInfo message) - { - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var vals = message.Data.Split('|'); - - var itemId = vals[0]; - - long? positionTicks = null; - - if (vals.Length > 1) - { - long pos; - - if (long.TryParse(vals[1], out pos)) - { - positionTicks = pos; - } - } - - var isPaused = vals.Length > 2 && string.Equals(vals[2], "true", StringComparison.OrdinalIgnoreCase); - var isMuted = vals.Length > 3 && string.Equals(vals[3], "true", StringComparison.OrdinalIgnoreCase); - - var info = new PlaybackProgressInfo - { - ItemId = itemId, - PositionTicks = positionTicks, - IsMuted = isMuted, - IsPaused = isPaused, - SessionId = session.Id - }; - - if (vals.Length > 4) - { - info.MediaSourceId = vals[4]; - } - - if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[5])) - { - info.VolumeLevel = int.Parse(vals[5], _usCulture); - } - - if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[6])) - { - info.AudioStreamIndex = int.Parse(vals[6], _usCulture); - } - - if (vals.Length > 7 && !string.IsNullOrWhiteSpace(vals[7])) - { - info.SubtitleStreamIndex = int.Parse(vals[7], _usCulture); - } - - _sessionManager.OnPlaybackProgress(info); - } - } - - private void ReportPlaybackStopped(WebSocketMessageInfo message) - { - _logger.Debug("Received ReportPlaybackStopped message"); - - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var info = _json.DeserializeFromString<PlaybackStopInfo>(message.Data); - - info.SessionId = session.Id; - - _sessionManager.OnPlaybackStopped(info); - } - } - - /// <summary> - /// Reports the playback stopped. - /// </summary> - /// <param name="message">The message.</param> - private void OnPlaybackStopped(WebSocketMessageInfo message) - { - _logger.Debug("Received PlaybackStopped message"); - - var session = GetSessionFromMessage(message); - - if (session != null && session.UserId.HasValue) - { - var vals = message.Data.Split('|'); - - var itemId = vals[0]; - - long? positionTicks = null; - - if (vals.Length > 1) - { - long pos; - - if (long.TryParse(vals[1], out pos)) - { - positionTicks = pos; - } - } - - var info = new PlaybackStopInfo - { - ItemId = itemId, - PositionTicks = positionTicks, - SessionId = session.Id - }; - - if (vals.Length > 2) - { - info.MediaSourceId = vals[2]; - } - - _sessionManager.OnPlaybackStopped(info); - } + var controller = (WebSocketController)controllerInfo.Item1; + controller.AddWebSocket(connection); } } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index b13eb6116..ddac9660f 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Net.WebSockets; namespace Emby.Server.Implementations.Session { @@ -40,28 +41,14 @@ namespace Emby.Server.Implementations.Session get { return HasOpenSockets; } } - private bool _isActive; - private DateTime _lastActivityDate; public bool IsSessionActive { get { - if (HasOpenSockets) - { - return true; - } - - //return false; - return _isActive && (DateTime.UtcNow - _lastActivityDate).TotalMinutes <= 10; + return HasOpenSockets; } } - public void OnActivity() - { - _isActive = true; - _lastActivityDate = DateTime.UtcNow; - } - private IEnumerable<IWebSocketConnection> GetActiveSockets() { return Sockets @@ -81,209 +68,40 @@ namespace Emby.Server.Implementations.Session void connection_Closed(object sender, EventArgs e) { - if (!GetActiveSockets().Any()) - { - _isActive = false; + var connection = (IWebSocketConnection)sender; + var sockets = Sockets.ToList(); + sockets.Remove(connection); - try - { - _sessionManager.ReportSessionEnded(Session.Id); - } - catch (Exception ex) - { - _logger.ErrorException("Error reporting session ended.", ex); - } - } + Sockets = sockets; + + _sessionManager.CloseIfNeeded(Session); } - private IWebSocketConnection GetActiveSocket() + public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) { var socket = GetActiveSockets() .FirstOrDefault(); if (socket == null) { - throw new InvalidOperationException("The requested session does not have an open web socket."); + return Task.CompletedTask; } - return socket; - } - - public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) - { - return SendMessageInternal(new WebSocketMessage<PlayRequest> - { - MessageType = "Play", - Data = command - - }, cancellationToken); - } - - public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) - { - return SendMessageInternal(new WebSocketMessage<PlaystateRequest> - { - MessageType = "Playstate", - Data = command - - }, cancellationToken); - } - - public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage<LibraryUpdateInfo> - { - MessageType = "LibraryChanged", - Data = info - - }, cancellationToken); - } - - /// <summary> - /// Sends the restart required message. - /// </summary> - /// <param name="info">The information.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task SendRestartRequiredNotification(CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage<string> - { - MessageType = "RestartRequired", - Data = string.Empty - - }, 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) - { - return SendMessagesInternal(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) - { - return SendMessagesInternal(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) - { - return SendMessagesInternal(new WebSocketMessage<string> - { - MessageType = "ServerRestarting", - Data = string.Empty - - }, cancellationToken); - } - - public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) - { - return SendMessageInternal(new WebSocketMessage<GeneralCommand> - { - MessageType = "GeneralCommand", - Data = command - - }, cancellationToken); - } - - public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage<SessionInfoDto> - { - MessageType = "SessionEnded", - Data = sessionInfo - - }, cancellationToken); - } - - public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage<SessionInfoDto> - { - MessageType = "PlaybackStart", - Data = sessionInfo - - }, cancellationToken); - } - - public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage<SessionInfoDto> - { - MessageType = "PlaybackStopped", - Data = sessionInfo - - }, cancellationToken); - } - - public Task SendMessage<T>(string name, T data, CancellationToken cancellationToken) - { - return SendMessagesInternal(new WebSocketMessage<T> + return socket.SendAsync(new WebSocketMessage<T> { Data = data, - MessageType = name + MessageType = name, + MessageId = messageId }, cancellationToken); } - private Task SendMessageInternal<T>(WebSocketMessage<T> message, CancellationToken cancellationToken) - { - var socket = GetActiveSocket(); - - return socket.SendAsync(message, cancellationToken); - } - - private Task SendMessagesInternal<T>(WebSocketMessage<T> message, CancellationToken cancellationToken) - { - var tasks = GetActiveSockets().Select(i => Task.Run(async () => - { - try - { - await i.SendAsync(message, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending web socket message", ex); - } - - }, cancellationToken)); - - return Task.WhenAll(tasks); - } - public void Dispose() { foreach (var socket in Sockets.ToList()) { socket.Closed -= connection_Closed; } - GC.SuppressFinalize(this); } } } |
