Browse Source

added ability to track web sockets per session

Luke Pulverenti 12 years ago
parent
commit
e1f8c18b51

+ 15 - 2
MediaBrowser.Api/BaseApiService.cs

@@ -1,5 +1,7 @@
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Logging;
 using ServiceStack.Common.Web;
 using ServiceStack.ServiceHost;
@@ -100,6 +102,8 @@ namespace MediaBrowser.Api
         /// <value>The user manager.</value>
         public IUserManager UserManager { get; set; }
 
+        public ISessionManager SessionManager { get; set; }
+
         /// <summary>
         /// Gets or sets the logger.
         /// </summary>
@@ -122,11 +126,20 @@ namespace MediaBrowser.Api
             {
                 var userId = auth["UserId"];
 
+                User user = null;
+
                 if (!string.IsNullOrEmpty(userId))
                 {
-                    var user = UserManager.GetUserById(new Guid(userId));
+                    user = UserManager.GetUserById(new Guid(userId));
+                }
 
-                    UserManager.LogUserActivity(user, auth["Client"], auth["DeviceId"], auth["Device"] ?? string.Empty);
+                var deviceId = auth["DeviceId"];
+                var device = auth["Device"];
+                var client = auth["Client"];
+
+                if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device))
+                {
+                    SessionManager.LogConnectionActivity(client, deviceId, device, user);
                 }
             }
         }

+ 1 - 0
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -88,6 +88,7 @@
     <Compile Include="ScheduledTasks\ScheduledTasksWebSocketListener.cs" />
     <Compile Include="ApiEntryPoint.cs" />
     <Compile Include="SearchService.cs" />
+    <Compile Include="SessionsService.cs" />
     <Compile Include="SystemService.cs" />
     <Compile Include="TvShowsService.cs" />
     <Compile Include="UserLibrary\ArtistsService.cs" />

+ 54 - 0
MediaBrowser.Api/SessionsService.cs

@@ -0,0 +1,54 @@
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Session;
+using ServiceStack.ServiceHost;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api
+{
+    /// <summary>
+    /// Class GetSessions
+    /// </summary>
+    [Route("/Sessions", "GET")]
+    [Api(("Gets a list of sessions"))]
+    public class GetSessions : IReturn<List<SessionInfo>>
+    {
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is recent.
+        /// </summary>
+        /// <value><c>true</c> if this instance is recent; otherwise, <c>false</c>.</value>
+        public bool IsRecent { get; set; }
+    }
+
+    /// <summary>
+    /// Class SessionsService
+    /// </summary>
+    public class SessionsService : BaseApiService
+    {
+        /// <summary>
+        /// The _session manager
+        /// </summary>
+        private readonly ISessionManager _sessionManager;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SessionsService"/> class.
+        /// </summary>
+        /// <param name="sessionManager">The session manager.</param>
+        public SessionsService(ISessionManager sessionManager)
+        {
+            _sessionManager = sessionManager;
+        }
+
+        /// <summary>
+        /// Gets the specified request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>System.Object.</returns>
+        public object Get(GetSessions request)
+        {
+            var result = request.IsRecent ? _sessionManager.RecentConnections : _sessionManager.AllConnections;
+
+            return ToOptimizedResult(result.ToList());
+        }
+    }
+}

+ 8 - 4
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using ServiceStack.ServiceHost;
@@ -401,6 +402,8 @@ namespace MediaBrowser.Api.UserLibrary
 
         private readonly IItemRepository _itemRepo;
 
+        private readonly ISessionManager _sessionManager;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="UserLibraryService" /> class.
         /// </summary>
@@ -409,13 +412,14 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="userDataRepository">The user data repository.</param>
         /// <param name="itemRepo">The item repo.</param>
         /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
-        public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo)
+        public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo, ISessionManager sessionManager)
             : base()
         {
             _userManager = userManager;
             _libraryManager = libraryManager;
             _userDataRepository = userDataRepository;
             _itemRepo = itemRepo;
+            _sessionManager = sessionManager;
         }
 
         /// <summary>
@@ -693,7 +697,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             if (auth != null)
             {
-                _userManager.OnPlaybackStart(user, item, auth["Client"], auth["DeviceId"], auth["Device"] ?? string.Empty);
+                _sessionManager.OnPlaybackStart(user, item, auth["Client"], auth["DeviceId"], auth["Device"] ?? string.Empty);
             }
         }
 
@@ -711,7 +715,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             if (auth != null)
             {
-                var task = _userManager.OnPlaybackProgress(user, item, request.PositionTicks, auth["Client"], auth["DeviceId"], auth["Device"] ?? string.Empty);
+                var task = _sessionManager.OnPlaybackProgress(user, item, request.PositionTicks, auth["Client"], auth["DeviceId"], auth["Device"] ?? string.Empty);
 
                 Task.WaitAll(task);
             }
@@ -731,7 +735,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             if (auth != null)
             {
-                var task = _userManager.OnPlaybackStopped(user, item, request.PositionTicks, auth["Client"], auth["DeviceId"], auth["Device"] ?? string.Empty);
+                var task = _sessionManager.OnPlaybackStopped(user, item, request.PositionTicks, auth["Client"], auth["DeviceId"], auth["Device"] ?? string.Empty);
 
                 Task.WaitAll(task);
             }

+ 1 - 10
MediaBrowser.Controller/Dto/DtoBuilder.cs

@@ -832,6 +832,7 @@ namespace MediaBrowser.Controller.Dto
             {
                 Id = GetClientItemId(item),
                 Name = item.Name,
+                MediaType = item.MediaType,
                 Type = item.GetType().Name,
                 IsFolder = item.IsFolder,
                 RunTimeTicks = item.RunTimeTicks
@@ -844,16 +845,6 @@ namespace MediaBrowser.Controller.Dto
                 info.PrimaryImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Primary, imagePath);
             }
 
-            if (item.BackdropImagePaths != null && item.BackdropImagePaths.Count > 0)
-            {
-                imagePath = item.BackdropImagePaths[0];
-
-                if (!string.IsNullOrEmpty(imagePath))
-                {
-                    info.BackdropImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Backdrop, imagePath);
-                }
-            }
-
             return info;
         }
 

+ 3 - 3
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -683,7 +683,7 @@ namespace MediaBrowser.Controller.Entities
         /// Loads local trailers from the file system
         /// </summary>
         /// <returns>List{Video}.</returns>
-        private List<Trailer> LoadLocalTrailers()
+        private IEnumerable<Trailer> LoadLocalTrailers()
         {
             if (LocationType != LocationType.FileSystem)
             {
@@ -746,7 +746,7 @@ namespace MediaBrowser.Controller.Entities
         /// Loads the theme songs.
         /// </summary>
         /// <returns>List{Audio.Audio}.</returns>
-        private List<Audio.Audio> LoadThemeSongs()
+        private IEnumerable<Audio.Audio> LoadThemeSongs()
         {
             if (LocationType != LocationType.FileSystem)
             {
@@ -809,7 +809,7 @@ namespace MediaBrowser.Controller.Entities
         /// Loads the video backdrops.
         /// </summary>
         /// <returns>List{Video}.</returns>
-        private List<Video> LoadThemeVideos()
+        private IEnumerable<Video> LoadThemeVideos()
         {
             if (LocationType != LocationType.FileSystem)
             {

+ 0 - 55
MediaBrowser.Controller/Library/IUserManager.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.Events;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Connectivity;
 using System;
 using System.Collections.Generic;
 using System.Threading;
@@ -19,12 +18,6 @@ namespace MediaBrowser.Controller.Library
         /// <value>The users.</value>
         IEnumerable<User> Users { get; }
 
-        /// <summary>
-        /// Gets the active connections.
-        /// </summary>
-        /// <value>The active connections.</value>
-        IEnumerable<ClientConnectionInfo> RecentConnections { get; }
-
         /// <summary>
         /// Occurs when [playback start].
         /// </summary>
@@ -67,17 +60,6 @@ namespace MediaBrowser.Controller.Library
         /// <exception cref="System.ArgumentNullException">user</exception>
         Task<bool> AuthenticateUser(User user, string password);
 
-        /// <summary>
-        /// Logs the user activity.
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException">user</exception>
-        Task LogUserActivity(User user, string clientType, string deviceId, string deviceName);
-
         /// <summary>
         /// Refreshes metadata for each user
         /// </summary>
@@ -122,43 +104,6 @@ namespace MediaBrowser.Controller.Library
         /// <exception cref="System.ArgumentException"></exception>
         Task DeleteUser(User user);
 
-        /// <summary>
-        /// Used to report that playback has started for an item
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="item">The item.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        void OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName);
-
-        /// <summary>
-        /// Used to report playback progress for an item
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="item">The item.</param>
-        /// <param name="positionTicks">The position ticks.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        Task OnPlaybackProgress(User user, BaseItem item, long? positionTicks, string clientType, string deviceId, string deviceName);
-
-        /// <summary>
-        /// Used to report that playback has ended for an item
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="item">The item.</param>
-        /// <param name="positionTicks">The position ticks.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        Task OnPlaybackStopped(User user, BaseItem item, long? positionTicks, string clientType, string deviceId, string deviceName);
-
         /// <summary>
         /// Resets the password.
         /// </summary>

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -70,6 +70,7 @@
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
     <Compile Include="Configuration\IServerConfigurationManager.cs" />
+    <Compile Include="Session\ISessionManager.cs" />
     <Compile Include="Drawing\ImageExtensions.cs" />
     <Compile Include="Drawing\ImageHeader.cs" />
     <Compile Include="Drawing\ImageManager.cs" />

+ 90 - 0
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -0,0 +1,90 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Session
+{
+    /// <summary>
+    /// Interface ISessionManager
+    /// </summary>
+    public interface ISessionManager
+    {
+        /// <summary>
+        /// Occurs when [playback start].
+        /// </summary>
+        event EventHandler<PlaybackProgressEventArgs> PlaybackStart;
+
+        /// <summary>
+        /// Occurs when [playback progress].
+        /// </summary>
+        event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
+
+        /// <summary>
+        /// Occurs when [playback stopped].
+        /// </summary>
+        event EventHandler<PlaybackProgressEventArgs> PlaybackStopped;
+
+        /// <summary>
+        /// Gets all connections.
+        /// </summary>
+        /// <value>All connections.</value>
+        IEnumerable<SessionInfo> AllConnections { get; }
+
+        /// <summary>
+        /// Gets the active connections.
+        /// </summary>
+        /// <value>The active connections.</value>
+        IEnumerable<SessionInfo> RecentConnections { get; }
+
+        /// <summary>
+        /// Logs the user activity.
+        /// </summary>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException">user</exception>
+        Task LogConnectionActivity(string clientType, string deviceId, string deviceName, User user);
+
+        /// <summary>
+        /// Used to report that playback has started for an item
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="item">The item.</param>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        void OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName);
+
+        /// <summary>
+        /// Used to report playback progress for an item
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="item">The item.</param>
+        /// <param name="positionTicks">The position ticks.</param>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        Task OnPlaybackProgress(User user, BaseItem item, long? positionTicks, string clientType, string deviceId, string deviceName);
+
+        /// <summary>
+        /// Used to report that playback has ended for an item
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="item">The item.</param>
+        /// <param name="positionTicks">The position ticks.</param>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        Task OnPlaybackStopped(User user, BaseItem item, long? positionTicks, string clientType, string deviceId, string deviceName);
+    }
+}

+ 6 - 16
MediaBrowser.Model/Entities/BaseItemInfo.cs

@@ -26,6 +26,12 @@ namespace MediaBrowser.Model.Entities
         /// <value>The type.</value>
         public string Type { get; set; }
 
+        /// <summary>
+        /// Gets or sets the type of the media.
+        /// </summary>
+        /// <value>The type of the media.</value>
+        public string MediaType { get; set; }
+        
         /// <summary>
         /// Gets or sets a value indicating whether this instance is folder.
         /// </summary>
@@ -44,12 +50,6 @@ namespace MediaBrowser.Model.Entities
         /// <value>The primary image tag.</value>
         public Guid? PrimaryImageTag { get; set; }
 
-        /// <summary>
-        /// Gets or sets the backdrop image tag.
-        /// </summary>
-        /// <value>The backdrop image tag.</value>
-        public Guid? BackdropImageTag { get; set; }
-
         /// <summary>
         /// Gets a value indicating whether this instance has primary image.
         /// </summary>
@@ -59,15 +59,5 @@ namespace MediaBrowser.Model.Entities
         {
             get { return PrimaryImageTag.HasValue; }
         }
-
-        /// <summary>
-        /// Gets a value indicating whether this instance has backdrop.
-        /// </summary>
-        /// <value><c>true</c> if this instance has backdrop; otherwise, <c>false</c>.</value>
-        [IgnoreDataMember]
-        public bool HasBackdrop
-        {
-            get { return BackdropImageTag.HasValue; }
-        }
     }
 }

+ 1 - 1
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -56,7 +56,7 @@
     <Compile Include="Querying\ArtistsQuery.cs" />
     <Compile Include="Querying\ItemsByNameQuery.cs" />
     <Compile Include="Entities\BaseItemInfo.cs" />
-    <Compile Include="Connectivity\ClientConnectionInfo.cs" />
+    <Compile Include="Session\SessionInfo.cs" />
     <Compile Include="Entities\ImageDownloadOptions.cs" />
     <Compile Include="Logging\ILogManager.cs" />
     <Compile Include="MediaInfo\BlurayDiscInfo.cs" />

+ 9 - 3
MediaBrowser.Model/Connectivity/ClientConnectionInfo.cs → MediaBrowser.Model/Session/SessionInfo.cs

@@ -1,13 +1,19 @@
 using MediaBrowser.Model.Entities;
 using System;
 
-namespace MediaBrowser.Model.Connectivity
+namespace MediaBrowser.Model.Session
 {
     /// <summary>
-    /// Class ClientConnectionInfo
+    /// Class SessionInfo
     /// </summary>
-    public class ClientConnectionInfo
+    public class SessionInfo
     {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        public Guid Id { get; set; }
+
         /// <summary>
         /// Gets or sets the user id.
         /// </summary>

+ 6 - 296
MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -7,7 +7,6 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Model.Connectivity;
 using MediaBrowser.Model.Logging;
 using System;
 using System.Collections.Concurrent;
@@ -17,6 +16,7 @@ using System.Security.Cryptography;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Session;
 
 namespace MediaBrowser.Server.Implementations.Library
 {
@@ -28,8 +28,8 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <summary>
         /// The _active connections
         /// </summary>
-        private readonly ConcurrentDictionary<string, ClientConnectionInfo> _activeConnections =
-            new ConcurrentDictionary<string, ClientConnectionInfo>(StringComparer.OrdinalIgnoreCase);
+        private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections =
+            new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase);
 
         /// <summary>
         /// The _users
@@ -70,7 +70,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// Gets all connections.
         /// </summary>
         /// <value>All connections.</value>
-        public IEnumerable<ClientConnectionInfo> AllConnections
+        public IEnumerable<SessionInfo> AllConnections
         {
             get { return _activeConnections.Values.OrderByDescending(c => c.LastActivityDate); }
         }
@@ -79,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// Gets the active connections.
         /// </summary>
         /// <value>The active connections.</value>
-        public IEnumerable<ClientConnectionInfo> RecentConnections
+        public IEnumerable<SessionInfo> RecentConnections
         {
             get { return AllConnections.Where(c => (DateTime.UtcNow - c.LastActivityDate).TotalMinutes <= 5); }
         }
@@ -89,8 +89,6 @@ namespace MediaBrowser.Server.Implementations.Library
         /// </summary>
         private readonly ILogger _logger;
 
-        private readonly IUserDataRepository _userDataRepository;
-        
         /// <summary>
         /// Gets or sets the configuration manager.
         /// </summary>
@@ -109,11 +107,10 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <param name="logger">The logger.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="userDataRepository">The user data repository.</param>
-        public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserDataRepository userDataRepository)
+        public UserManager(ILogger logger, IServerConfigurationManager configurationManager)
         {
             _logger = logger;
             ConfigurationManager = configurationManager;
-            _userDataRepository = userDataRepository;
         }
 
         #region Events
@@ -222,116 +219,6 @@ namespace MediaBrowser.Server.Implementations.Library
             }
         }
 
-        /// <summary>
-        /// Logs the user activity.
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException">user</exception>
-        public Task LogUserActivity(User user, string clientType, string deviceId, string deviceName)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException("user");
-            }
-
-            var activityDate = DateTime.UtcNow;
-
-            var lastActivityDate = user.LastActivityDate;
-
-            user.LastActivityDate = activityDate;
-
-            LogConnection(user.Id, clientType, deviceId, deviceName, activityDate);
-
-            // Don't log in the db anymore frequently than 10 seconds
-            if (lastActivityDate.HasValue && (activityDate - lastActivityDate.Value).TotalSeconds < 10)
-            {
-                return Task.FromResult(true);
-            }
-
-            // Save this directly. No need to fire off all the events for this.
-            return UserRepository.SaveUser(user, CancellationToken.None);
-        }
-
-        /// <summary>
-        /// Updates the now playing item id.
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <param name="item">The item.</param>
-        /// <param name="currentPositionTicks">The current position ticks.</param>
-        private void UpdateNowPlayingItemId(User user, string clientType, string deviceId, string deviceName, BaseItem item, long? currentPositionTicks = null)
-        {
-            var conn = GetConnection(user.Id, clientType, deviceId, deviceName);
-
-            conn.NowPlayingPositionTicks = currentPositionTicks;
-            conn.NowPlayingItem = DtoBuilder.GetBaseItemInfo(item);
-            conn.LastActivityDate = DateTime.UtcNow;
-        }
-
-        /// <summary>
-        /// Removes the now playing item id.
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <param name="item">The item.</param>
-        private void RemoveNowPlayingItemId(User user, string clientType, string deviceId, string deviceName, BaseItem item)
-        {
-            var conn = GetConnection(user.Id, clientType, deviceId, deviceName);
-
-            if (conn.NowPlayingItem != null && conn.NowPlayingItem.Id.Equals(item.Id.ToString()))
-            {
-                conn.NowPlayingItem = null;
-                conn.NowPlayingPositionTicks = null;
-            }
-        }
-
-        /// <summary>
-        /// Logs the connection.
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <param name="lastActivityDate">The last activity date.</param>
-        private void LogConnection(Guid userId, string clientType, string deviceId, string deviceName, DateTime lastActivityDate)
-        {
-            GetConnection(userId, clientType, deviceId, deviceName).LastActivityDate = lastActivityDate;
-        }
-
-        /// <summary>
-        /// Gets the connection.
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <returns>ClientConnectionInfo.</returns>
-        private ClientConnectionInfo GetConnection(Guid userId, string clientType, string deviceId, string deviceName)
-        {
-            var key = clientType + deviceId;
-
-            var connection = _activeConnections.GetOrAdd(key, keyName => new ClientConnectionInfo
-            {
-                UserId = userId.ToString(),
-                Client = clientType,
-                DeviceName = deviceName,
-                DeviceId = deviceId
-            });
-
-            connection.DeviceName = deviceName;
-            connection.UserId = userId.ToString();
-            
-            return connection;
-        }
-
         /// <summary>
         /// Loads the users from the repository
         /// </summary>
@@ -560,182 +447,5 @@ namespace MediaBrowser.Server.Implementations.Library
                 DateModified = DateTime.UtcNow
             };
         }
-
-        /// <summary>
-        /// Used to report that playback has started for an item
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="item">The item.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public void OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException();
-            }
-            if (item == null)
-            {
-                throw new ArgumentNullException();
-            }
-
-            UpdateNowPlayingItemId(user, clientType, deviceId, deviceName, item);
-
-            // Nothing to save here
-            // Fire events to inform plugins
-            EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
-            {
-                Item = item,
-                User = user
-            }, _logger);
-        }
-
-        /// <summary>
-        /// Used to report playback progress for an item
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="item">The item.</param>
-        /// <param name="positionTicks">The position ticks.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public async Task OnPlaybackProgress(User user, BaseItem item, long? positionTicks, string clientType, string deviceId, string deviceName)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException();
-            }
-            if (item == null)
-            {
-                throw new ArgumentNullException();
-            }
-
-            UpdateNowPlayingItemId(user, clientType, deviceId, deviceName, item, positionTicks);
-
-            var key = item.GetUserDataKey();
-            
-            if (positionTicks.HasValue)
-            {
-                var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
-
-                UpdatePlayState(item, data, positionTicks.Value, false);
-                await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
-            }
-
-            EventHelper.QueueEventIfNotNull(PlaybackProgress, this, new PlaybackProgressEventArgs
-            {
-                Item = item,
-                User = user,
-                PlaybackPositionTicks = positionTicks
-            }, _logger);
-        }
-
-        /// <summary>
-        /// Used to report that playback has ended for an item
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="item">The item.</param>
-        /// <param name="positionTicks">The position ticks.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public async Task OnPlaybackStopped(User user, BaseItem item, long? positionTicks, string clientType, string deviceId, string deviceName)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException();
-            }
-            if (item == null)
-            {
-                throw new ArgumentNullException();
-            }
-
-            RemoveNowPlayingItemId(user, clientType, deviceId, deviceName, item);
-
-            var key = item.GetUserDataKey();
-
-            var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
-
-            if (positionTicks.HasValue)
-            {
-                UpdatePlayState(item, data, positionTicks.Value, true);
-            }
-            else
-            {
-                // If the client isn't able to report this, then we'll just have to make an assumption
-                data.PlayCount++;
-                data.Played = true;
-            }
-
-            await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
-
-            EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackProgressEventArgs
-            {
-                Item = item,
-                User = user,
-                PlaybackPositionTicks = positionTicks
-            }, _logger);
-        }
-
-        /// <summary>
-        /// Updates playstate position for an item but does not save
-        /// </summary>
-        /// <param name="item">The item</param>
-        /// <param name="data">User data for the item</param>
-        /// <param name="positionTicks">The current playback position</param>
-        /// <param name="incrementPlayCount">Whether or not to increment playcount</param>
-        private void UpdatePlayState(BaseItem item, UserItemData data, long positionTicks, bool incrementPlayCount)
-        {
-            // If a position has been reported, and if we know the duration
-            if (positionTicks > 0 && item.RunTimeTicks.HasValue && item.RunTimeTicks > 0)
-            {
-                var pctIn = Decimal.Divide(positionTicks, item.RunTimeTicks.Value) * 100;
-
-                // Don't track in very beginning
-                if (pctIn < ConfigurationManager.Configuration.MinResumePct)
-                {
-                    positionTicks = 0;
-                    incrementPlayCount = false;
-                }
-
-                // If we're at the end, assume completed
-                else if (pctIn > ConfigurationManager.Configuration.MaxResumePct || positionTicks >= item.RunTimeTicks.Value)
-                {
-                    positionTicks = 0;
-                    data.Played = true;
-                }
-
-                else
-                {
-                    // Enforce MinResumeDuration
-                    var durationSeconds = TimeSpan.FromTicks(item.RunTimeTicks.Value).TotalSeconds;
-
-                    if (durationSeconds < ConfigurationManager.Configuration.MinResumeDurationSeconds)
-                    {
-                        positionTicks = 0;
-                        data.Played = true;
-                    }
-                }
-            }
-
-            if (item is Audio)
-            {
-                data.PlaybackPositionTicks = 0;
-            }
-
-            data.PlaybackPositionTicks = positionTicks;
-
-            if (incrementPlayCount)
-            {
-                data.PlayCount++;
-                data.LastPlayedDate = DateTime.UtcNow;
-            }
-        }
     }
 }

+ 4 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -156,6 +156,10 @@
     <Compile Include="ServerApplicationPaths.cs" />
     <Compile Include="ServerManager\ServerManager.cs" />
     <Compile Include="ServerManager\WebSocketConnection.cs" />
+    <Compile Include="Session\SessionManager.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Session\SessionWebSocketListener.cs" />
     <Compile Include="Sorting\AlbumArtistComparer.cs" />
     <Compile Include="Sorting\AlbumComparer.cs" />
     <Compile Include="Sorting\ArtistComparer.cs" />

+ 372 - 0
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -0,0 +1,372 @@
+using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Session;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Session
+{
+    public class SessionManager : ISessionManager
+    {
+        private readonly IUserDataRepository _userDataRepository;
+
+        private readonly IUserRepository _userRepository;
+
+        /// <summary>
+        /// The _logger
+        /// </summary>
+        private readonly ILogger _logger;
+
+        /// <summary>
+        /// Gets or sets the configuration manager.
+        /// </summary>
+        /// <value>The configuration manager.</value>
+        private readonly IServerConfigurationManager _configurationManager;
+
+        /// <summary>
+        /// The _active connections
+        /// </summary>
+        private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections =
+            new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase);
+
+        private readonly ConcurrentDictionary<Guid, IWebSocketConnection> _websocketConnections =
+         new ConcurrentDictionary<Guid, IWebSocketConnection>();
+        
+        /// <summary>
+        /// Occurs when [playback start].
+        /// </summary>
+        public event EventHandler<PlaybackProgressEventArgs> PlaybackStart;
+        /// <summary>
+        /// Occurs when [playback progress].
+        /// </summary>
+        public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
+        /// <summary>
+        /// Occurs when [playback stopped].
+        /// </summary>
+        public event EventHandler<PlaybackProgressEventArgs> PlaybackStopped;
+
+        public SessionManager(IUserDataRepository userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository)
+        {
+            _userDataRepository = userDataRepository;
+            _configurationManager = configurationManager;
+            _logger = logger;
+            _userRepository = userRepository;
+        }
+
+        /// <summary>
+        /// Gets all connections.
+        /// </summary>
+        /// <value>All connections.</value>
+        public IEnumerable<SessionInfo> AllConnections
+        {
+            get { return _activeConnections.Values.OrderByDescending(c => c.LastActivityDate); }
+        }
+
+        /// <summary>
+        /// Gets the active connections.
+        /// </summary>
+        /// <value>The active connections.</value>
+        public IEnumerable<SessionInfo> RecentConnections
+        {
+            get { return AllConnections.Where(c => (DateTime.UtcNow - c.LastActivityDate).TotalMinutes <= 5); }
+        }
+
+        private readonly Task _trueTaskResult = Task.FromResult(true);
+
+        /// <summary>
+        /// Logs the user activity.
+        /// </summary>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException">user</exception>
+        public Task LogConnectionActivity(string clientType, string deviceId, string deviceName, User user)
+        {
+            var activityDate = DateTime.UtcNow;
+
+            GetConnection(clientType, deviceId, deviceName, user).LastActivityDate = activityDate;
+
+            if (user == null)
+            {
+                return _trueTaskResult;
+            }
+
+            var lastActivityDate = user.LastActivityDate;
+
+            user.LastActivityDate = activityDate;
+
+            // Don't log in the db anymore frequently than 10 seconds
+            if (lastActivityDate.HasValue && (activityDate - lastActivityDate.Value).TotalSeconds < 10)
+            {
+                return _trueTaskResult;
+            }
+
+            // Save this directly. No need to fire off all the events for this.
+            return _userRepository.SaveUser(user, CancellationToken.None);
+        }
+
+        /// <summary>
+        /// Updates the now playing item id.
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="item">The item.</param>
+        /// <param name="currentPositionTicks">The current position ticks.</param>
+        private void UpdateNowPlayingItemId(User user, string clientType, string deviceId, string deviceName, BaseItem item, long? currentPositionTicks = null)
+        {
+            var conn = GetConnection(clientType, deviceId, deviceName, user);
+
+            conn.NowPlayingPositionTicks = currentPositionTicks;
+            conn.NowPlayingItem = DtoBuilder.GetBaseItemInfo(item);
+            conn.LastActivityDate = DateTime.UtcNow;
+        }
+
+        /// <summary>
+        /// Removes the now playing item id.
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="item">The item.</param>
+        private void RemoveNowPlayingItemId(User user, string clientType, string deviceId, string deviceName, BaseItem item)
+        {
+            var conn = GetConnection(clientType, deviceId, deviceName, user);
+
+            if (conn.NowPlayingItem != null && conn.NowPlayingItem.Id.Equals(item.Id.ToString()))
+            {
+                conn.NowPlayingItem = null;
+                conn.NowPlayingPositionTicks = null;
+            }
+        }
+
+        /// <summary>
+        /// Gets the connection.
+        /// </summary>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="user">The user.</param>
+        /// <returns>SessionInfo.</returns>
+        private SessionInfo GetConnection(string clientType, string deviceId, string deviceName, User user)
+        {
+            var key = clientType + deviceId;
+
+            var connection = _activeConnections.GetOrAdd(key, keyName => new SessionInfo
+            {
+                Client = clientType,
+                DeviceId = deviceId,
+                Id = Guid.NewGuid()
+            });
+
+            connection.DeviceName = deviceName;
+
+            connection.UserId = user == null ? null : user.Id.ToString();
+
+            return connection;
+        }
+
+        /// <summary>
+        /// Used to report that playback has started for an item
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="item">The item.</param>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        public void OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName)
+        {
+            if (user == null)
+            {
+                throw new ArgumentNullException();
+            }
+            if (item == null)
+            {
+                throw new ArgumentNullException();
+            }
+
+            UpdateNowPlayingItemId(user, clientType, deviceId, deviceName, item);
+
+            // Nothing to save here
+            // Fire events to inform plugins
+            EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
+            {
+                Item = item,
+                User = user
+            }, _logger);
+        }
+
+        /// <summary>
+        /// Used to report playback progress for an item
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="item">The item.</param>
+        /// <param name="positionTicks">The position ticks.</param>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        public async Task OnPlaybackProgress(User user, BaseItem item, long? positionTicks, string clientType, string deviceId, string deviceName)
+        {
+            if (user == null)
+            {
+                throw new ArgumentNullException();
+            }
+            if (item == null)
+            {
+                throw new ArgumentNullException();
+            }
+
+            UpdateNowPlayingItemId(user, clientType, deviceId, deviceName, item, positionTicks);
+
+            var key = item.GetUserDataKey();
+
+            if (positionTicks.HasValue)
+            {
+                var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
+
+                UpdatePlayState(item, data, positionTicks.Value, false);
+                await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
+            }
+
+            EventHelper.QueueEventIfNotNull(PlaybackProgress, this, new PlaybackProgressEventArgs
+            {
+                Item = item,
+                User = user,
+                PlaybackPositionTicks = positionTicks
+            }, _logger);
+        }
+
+        /// <summary>
+        /// Used to report that playback has ended for an item
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="item">The item.</param>
+        /// <param name="positionTicks">The position ticks.</param>
+        /// <param name="clientType">Type of the client.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="deviceName">Name of the device.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        public async Task OnPlaybackStopped(User user, BaseItem item, long? positionTicks, string clientType, string deviceId, string deviceName)
+        {
+            if (user == null)
+            {
+                throw new ArgumentNullException();
+            }
+            if (item == null)
+            {
+                throw new ArgumentNullException();
+            }
+
+            RemoveNowPlayingItemId(user, clientType, deviceId, deviceName, item);
+
+            var key = item.GetUserDataKey();
+
+            var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
+
+            if (positionTicks.HasValue)
+            {
+                UpdatePlayState(item, data, positionTicks.Value, true);
+            }
+            else
+            {
+                // If the client isn't able to report this, then we'll just have to make an assumption
+                data.PlayCount++;
+                data.Played = true;
+            }
+
+            await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
+
+            EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackProgressEventArgs
+            {
+                Item = item,
+                User = user,
+                PlaybackPositionTicks = positionTicks
+            }, _logger);
+        }
+
+        /// <summary>
+        /// Updates playstate position for an item but does not save
+        /// </summary>
+        /// <param name="item">The item</param>
+        /// <param name="data">User data for the item</param>
+        /// <param name="positionTicks">The current playback position</param>
+        /// <param name="incrementPlayCount">Whether or not to increment playcount</param>
+        private void UpdatePlayState(BaseItem item, UserItemData data, long positionTicks, bool incrementPlayCount)
+        {
+            // If a position has been reported, and if we know the duration
+            if (positionTicks > 0 && item.RunTimeTicks.HasValue && item.RunTimeTicks > 0)
+            {
+                var pctIn = Decimal.Divide(positionTicks, item.RunTimeTicks.Value) * 100;
+
+                // Don't track in very beginning
+                if (pctIn < _configurationManager.Configuration.MinResumePct)
+                {
+                    positionTicks = 0;
+                    incrementPlayCount = false;
+                }
+
+                // If we're at the end, assume completed
+                else if (pctIn > _configurationManager.Configuration.MaxResumePct || positionTicks >= item.RunTimeTicks.Value)
+                {
+                    positionTicks = 0;
+                    data.Played = true;
+                }
+
+                else
+                {
+                    // Enforce MinResumeDuration
+                    var durationSeconds = TimeSpan.FromTicks(item.RunTimeTicks.Value).TotalSeconds;
+
+                    if (durationSeconds < _configurationManager.Configuration.MinResumeDurationSeconds)
+                    {
+                        positionTicks = 0;
+                        data.Played = true;
+                    }
+                }
+            }
+
+            if (item is Audio)
+            {
+                data.PlaybackPositionTicks = 0;
+            }
+
+            data.PlaybackPositionTicks = positionTicks;
+
+            if (incrementPlayCount)
+            {
+                data.PlayCount++;
+                data.LastPlayedDate = DateTime.UtcNow;
+            }
+        }
+
+        /// <summary>
+        /// Identifies the web socket.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="webSocket">The web socket.</param>
+        public void IdentifyWebSocket(Guid sessionId, IWebSocketConnection webSocket)
+        {
+            _websocketConnections.AddOrUpdate(sessionId, webSocket, (key, existing) => webSocket);
+        }
+    }
+}

+ 58 - 0
MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs

@@ -0,0 +1,58 @@
+using System.Linq;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Session;
+using System;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Session
+{
+    /// <summary>
+    /// Class SessionWebSocketListener
+    /// </summary>
+    public class SessionWebSocketListener : IWebSocketListener
+    {
+        /// <summary>
+        /// The _true task result
+        /// </summary>
+        private readonly Task _trueTaskResult = Task.FromResult(true);
+
+        /// <summary>
+        /// The _session manager
+        /// </summary>
+        private readonly ISessionManager _sessionManager;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SessionWebSocketListener"/> class.
+        /// </summary>
+        /// <param name="sessionManager">The session manager.</param>
+        public SessionWebSocketListener(ISessionManager sessionManager)
+        {
+            _sessionManager = sessionManager;
+        }
+
+        /// <summary>
+        /// Processes the message.
+        /// </summary>
+        /// <param name="message">The message.</param>
+        /// <returns>Task.</returns>
+        public Task ProcessMessage(WebSocketMessageInfo message)
+        {
+            if (string.Equals(message.MessageType, "Identify", StringComparison.OrdinalIgnoreCase))
+            {
+                var vals = message.Data.Split('|');
+
+                var deviceId = vals[0];
+                var client = vals[1];
+
+                var session = _sessionManager.AllConnections.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) && string.Equals(i.Client, client));
+
+                if (session != null)
+                {
+                    ((SessionManager)_sessionManager).IdentifyWebSocket(session.Id, message.Connection);
+                }
+            }
+
+            return _trueTaskResult;
+        }
+    }
+}

+ 6 - 1
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -20,6 +20,7 @@ using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Updates;
 using MediaBrowser.Controller.Weather;
@@ -37,6 +38,7 @@ using MediaBrowser.Server.Implementations.Library;
 using MediaBrowser.Server.Implementations.MediaEncoder;
 using MediaBrowser.Server.Implementations.Providers;
 using MediaBrowser.Server.Implementations.ServerManager;
+using MediaBrowser.Server.Implementations.Session;
 using MediaBrowser.Server.Implementations.Sqlite;
 using MediaBrowser.Server.Implementations.Udp;
 using MediaBrowser.Server.Implementations.Updates;
@@ -251,7 +253,7 @@ namespace MediaBrowser.ServerApplication
             ItemRepository = new SQLiteItemRepository(ApplicationPaths, JsonSerializer, LogManager);
             RegisterSingleInstance(ItemRepository);
 
-            UserManager = new UserManager(Logger, ServerConfigurationManager, UserDataRepository);
+            UserManager = new UserManager(Logger, ServerConfigurationManager);
             RegisterSingleInstance(UserManager);
 
             LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataRepository);
@@ -274,6 +276,9 @@ namespace MediaBrowser.ServerApplication
             MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ZipClient, ApplicationPaths, JsonSerializer);
             RegisterSingleInstance(MediaEncoder);
 
+            var clientConnectionManager = new SessionManager(UserDataRepository, ServerConfigurationManager, Logger, UserRepository);
+            RegisterSingleInstance<ISessionManager>(clientConnectionManager);
+
             HttpServer = await _httpServerCreationTask.ConfigureAwait(false);
             RegisterSingleInstance(HttpServer, false);
 

+ 3 - 3
MediaBrowser.WebDashboard/Api/DashboardInfo.cs

@@ -1,5 +1,5 @@
-using MediaBrowser.Model.Connectivity;
-using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Session;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.Tasks;
 using System;
@@ -33,7 +33,7 @@ namespace MediaBrowser.WebDashboard.Api
         /// Gets or sets the active connections.
         /// </summary>
         /// <value>The active connections.</value>
-        public ClientConnectionInfo[] ActiveConnections { get; set; }
+        public SessionInfo[] ActiveConnections { get; set; }
 
         /// <summary>
         /// Gets or sets the users.

+ 5 - 2
MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Logging;
 using System.ComponentModel.Composition;
 using System.Threading.Tasks;
@@ -36,6 +37,7 @@ namespace MediaBrowser.WebDashboard.Api
         /// </summary>
         private readonly IUserManager _userManager;
         private readonly ILibraryManager _libraryManager;
+        private readonly ISessionManager _sessionManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="DashboardInfoWebSocketListener" /> class.
@@ -45,13 +47,14 @@ namespace MediaBrowser.WebDashboard.Api
         /// <param name="taskManager">The task manager.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="libraryManager">The library manager.</param>
-        public DashboardInfoWebSocketListener(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, IUserManager userManager, ILibraryManager libraryManager)
+        public DashboardInfoWebSocketListener(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, IUserManager userManager, ILibraryManager libraryManager, ISessionManager sessionManager)
             : base(logger)
         {
             _appHost = appHost;
             _taskManager = taskManager;
             _userManager = userManager;
             _libraryManager = libraryManager;
+            _sessionManager = sessionManager;
         }
 
         /// <summary>
@@ -61,7 +64,7 @@ namespace MediaBrowser.WebDashboard.Api
         /// <returns>Task{IEnumerable{TaskInfo}}.</returns>
         protected override Task<DashboardInfo> GetDataToSend(object state)
         {
-            return DashboardService.GetDashboardInfo(_appHost, Logger, _taskManager, _userManager, _libraryManager);
+            return DashboardService.GetDashboardInfo(_appHost, Logger, _taskManager, _userManager, _libraryManager, _sessionManager);
         }
     }
 }

+ 14 - 4
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -7,6 +7,7 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Tasks;
 using ServiceStack.ServiceHost;
@@ -127,6 +128,8 @@ namespace MediaBrowser.WebDashboard.Api
         /// </summary>
         private readonly IServerConfigurationManager _serverConfigurationManager;
 
+        private readonly ISessionManager _sessionManager;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="DashboardService" /> class.
         /// </summary>
@@ -135,13 +138,14 @@ namespace MediaBrowser.WebDashboard.Api
         /// <param name="appHost">The app host.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="serverConfigurationManager">The server configuration manager.</param>
-        public DashboardService(ITaskManager taskManager, IUserManager userManager, IServerApplicationHost appHost, ILibraryManager libraryManager, IServerConfigurationManager serverConfigurationManager)
+        public DashboardService(ITaskManager taskManager, IUserManager userManager, IServerApplicationHost appHost, ILibraryManager libraryManager, IServerConfigurationManager serverConfigurationManager, ISessionManager sessionManager)
         {
             _taskManager = taskManager;
             _userManager = userManager;
             _appHost = appHost;
             _libraryManager = libraryManager;
             _serverConfigurationManager = serverConfigurationManager;
+            _sessionManager = sessionManager;
         }
 
         /// <summary>
@@ -180,7 +184,7 @@ namespace MediaBrowser.WebDashboard.Api
         /// <returns>System.Object.</returns>
         public object Get(GetDashboardInfo request)
         {
-            var result =  GetDashboardInfo(_appHost, Logger, _taskManager, _userManager, _libraryManager).Result;
+            var result =  GetDashboardInfo(_appHost, Logger, _taskManager, _userManager, _libraryManager, _sessionManager).Result;
 
             return ResultFactory.GetOptimizedResult(RequestContext, result);
         }
@@ -193,10 +197,16 @@ namespace MediaBrowser.WebDashboard.Api
         /// <param name="taskManager">The task manager.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="libraryManager">The library manager.</param>
+        /// <param name="connectionManager">The connection manager.</param>
         /// <returns>DashboardInfo.</returns>
-        public static async Task<DashboardInfo> GetDashboardInfo(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, IUserManager userManager, ILibraryManager libraryManager)
+        public static async Task<DashboardInfo> GetDashboardInfo(IServerApplicationHost appHost, 
+            ILogger logger, 
+            ITaskManager taskManager, 
+            IUserManager userManager, 
+            ILibraryManager libraryManager,
+            ISessionManager connectionManager)
         {
-            var connections = userManager.RecentConnections.ToArray();
+            var connections = connectionManager.RecentConnections.ToArray();
 
             var dtoBuilder = new UserDtoBuilder(logger);
 

+ 3 - 0
MediaBrowser.WebDashboard/ApiClient.js

@@ -159,6 +159,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
 
             webSocket.onopen = function () {
                 setTimeout(function () {
+
+                    self.sendWebSocketMessage("Identify", deviceId + "|" + clientName);
+
                     $(self).trigger("websocketopen");
                 }, 500);
             };

+ 1 - 1
MediaBrowser.WebDashboard/packages.config

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.99" targetFramework="net45" />
+  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.100" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.44" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.44" targetFramework="net45" />
 </packages>

+ 3 - 0
MediaBrowser.sln

@@ -173,4 +173,7 @@ Global
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(Performance) = preSolution
+		HasPerformanceSessions = true
+	EndGlobalSection
 EndGlobal