Browse Source

combined usermanager and userdata manager

LukePulverenti 12 years ago
parent
commit
ff4ee7ab9c
33 changed files with 705 additions and 520 deletions
  1. 1 2
      BDInfo/Properties/AssemblyInfo.cs
  2. 18 10
      MediaBrowser.Api/Images/ImageService.cs
  3. 15 17
      MediaBrowser.Api/Library/LibraryStructureService.cs
  4. 10 2
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  5. 3 2
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  6. 3 2
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  7. 3 2
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  8. 1 4
      MediaBrowser.Api/Streaming/BaseStreamingHandler.cs
  9. 16 4
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  10. 5 0
      MediaBrowser.Api/UserLibrary/GenresService.cs
  11. 16 4
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  12. 5 0
      MediaBrowser.Api/UserLibrary/PersonsService.cs
  13. 6 0
      MediaBrowser.Api/UserLibrary/StudiosService.cs
  14. 42 60
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  15. 5 0
      MediaBrowser.Api/UserLibrary/YearsService.cs
  16. 20 24
      MediaBrowser.Api/UserService.cs
  17. 2 2
      MediaBrowser.Controller/Entities/BaseItem.cs
  18. 3 3
      MediaBrowser.Controller/Entities/Folder.cs
  19. 9 6
      MediaBrowser.Controller/Entities/User.cs
  20. 7 72
      MediaBrowser.Controller/Kernel.cs
  21. 9 9
      MediaBrowser.Controller/Library/DtoBuilder.cs
  22. 169 0
      MediaBrowser.Controller/Library/IUserManager.cs
  23. 6 3
      MediaBrowser.Controller/Library/LibraryManager.cs
  24. 0 226
      MediaBrowser.Controller/Library/UserDataManager.cs
  25. 1 2
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  26. 269 22
      MediaBrowser.Server.Implementations/Library/UserManager.cs
  27. 1 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  28. 16 16
      MediaBrowser.ServerApplication/App.xaml.cs
  29. 4 0
      MediaBrowser.ServerApplication/ApplicationHost.cs
  30. 2 2
      MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs
  31. 12 17
      MediaBrowser.ServerApplication/MainWindow.xaml.cs
  32. 11 2
      MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs
  33. 15 5
      MediaBrowser.WebDashboard/Api/DashboardService.cs

+ 1 - 2
BDInfo/Properties/AssemblyInfo.cs

@@ -32,5 +32,4 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers 
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: AssemblyVersion("1.0.*")]

+ 18 - 10
MediaBrowser.Api/Images/ImageService.cs

@@ -136,6 +136,20 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     public class ImageService : BaseRestService
     {
+        /// <summary>
+        /// The _user manager
+        /// </summary>
+        private readonly IUserManager _userManager;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ImageService" /> class.
+        /// </summary>
+        /// <param name="userManager">The user manager.</param>
+        public ImageService(IUserManager userManager)
+        {
+            _userManager = userManager;
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>
@@ -143,7 +157,7 @@ namespace MediaBrowser.Api.Images
         /// <returns>System.Object.</returns>
         public object Get(GetItemImage request)
         {
-            var item = DtoBuilder.GetItemByClientId(request.Id);
+            var item = DtoBuilder.GetItemByClientId(request.Id, _userManager);
 
             return GetImage(request, item);
         }
@@ -155,9 +169,7 @@ namespace MediaBrowser.Api.Images
         /// <returns>System.Object.</returns>
         public object Get(GetUserImage request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var item = kernel.Users.First(i => i.Id == request.Id);
+            var item = _userManager.Users.First(i => i.Id == request.Id);
 
             return GetImage(request, item);
         }
@@ -224,14 +236,12 @@ namespace MediaBrowser.Api.Images
         /// <param name="request">The request.</param>
         public void Post(PostUserImage request)
         {
-            var kernel = (Kernel)Kernel;
-
             var pathInfo = PathInfo.Parse(Request.PathInfo);
             var id = new Guid(pathInfo.GetArgumentValue<string>(1));
 
             request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue<string>(3), true);
 
-            var item = kernel.Users.First(i => i.Id == id);
+            var item = _userManager.Users.First(i => i.Id == id);
 
             var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType);
 
@@ -244,9 +254,7 @@ namespace MediaBrowser.Api.Images
         /// <param name="request">The request.</param>
         public void Delete(DeleteUserImage request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var item = kernel.Users.First(i => i.Id == request.Id);
+            var item = _userManager.Users.First(i => i.Id == request.Id);
 
             var task = item.DeleteImage(request.Type);
 

+ 15 - 17
MediaBrowser.Api/Library/LibraryStructureService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Implementations.HttpServer;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;
 using ServiceStack.ServiceHost;
 using System;
@@ -135,18 +136,25 @@ namespace MediaBrowser.Api.Library
         /// </summary>
         private readonly IServerApplicationPaths _appPaths;
 
+        /// <summary>
+        /// The _user manager
+        /// </summary>
+        private readonly IUserManager _userManager;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryService" /> class.
         /// </summary>
         /// <param name="appPaths">The app paths.</param>
+        /// <param name="userManager">The user manager.</param>
         /// <exception cref="System.ArgumentNullException">appHost</exception>
-        public LibraryStructureService(IServerApplicationPaths appPaths)
+        public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager)
         {
             if (appPaths == null)
             {
                 throw new ArgumentNullException("appPaths");
             }
 
+            _userManager = userManager;
             _appPaths = appPaths;
         }
 
@@ -167,7 +175,7 @@ namespace MediaBrowser.Api.Library
             }
             else
             {
-                var user = kernel.GetUserById(new Guid(request.UserId));
+                var user = _userManager.GetUserById(new Guid(request.UserId));
 
                 var result = kernel.LibraryManager.GetVirtualFolders(user).ToList();
 
@@ -181,15 +189,13 @@ namespace MediaBrowser.Api.Library
         /// <param name="request">The request.</param>
         public void Post(AddVirtualFolder request)
         {
-            var kernel = (Kernel)Kernel;
-            
             if (string.IsNullOrEmpty(request.UserId))
             {
                 LibraryHelpers.AddVirtualFolder(request.Name, null, _appPaths);
             }
             else
             {
-                var user = kernel.GetUserById(new Guid(request.UserId));
+                var user = _userManager.GetUserById(new Guid(request.UserId));
 
                 LibraryHelpers.AddVirtualFolder(request.Name, user, _appPaths);
             }
@@ -201,15 +207,13 @@ namespace MediaBrowser.Api.Library
         /// <param name="request">The request.</param>
         public void Post(RenameVirtualFolder request)
         {
-            var kernel = (Kernel)Kernel;
-
             if (string.IsNullOrEmpty(request.UserId))
             {
                 LibraryHelpers.RenameVirtualFolder(request.Name, request.NewName, null, _appPaths);
             }
             else
             {
-                var user = kernel.GetUserById(new Guid(request.UserId));
+                var user = _userManager.GetUserById(new Guid(request.UserId));
 
                 LibraryHelpers.RenameVirtualFolder(request.Name, request.NewName, user, _appPaths);
             }
@@ -221,15 +225,13 @@ namespace MediaBrowser.Api.Library
         /// <param name="request">The request.</param>
         public void Delete(RemoveVirtualFolder request)
         {
-            var kernel = (Kernel)Kernel;
-
             if (string.IsNullOrEmpty(request.UserId))
             {
                 LibraryHelpers.RemoveVirtualFolder(request.Name, null, _appPaths);
             }
             else
             {
-                var user = kernel.GetUserById(new Guid(request.UserId));
+                var user = _userManager.GetUserById(new Guid(request.UserId));
 
                 LibraryHelpers.RemoveVirtualFolder(request.Name, user, _appPaths);
             }
@@ -241,15 +243,13 @@ namespace MediaBrowser.Api.Library
         /// <param name="request">The request.</param>
         public void Post(AddMediaPath request)
         {
-            var kernel = (Kernel)Kernel;
-
             if (string.IsNullOrEmpty(request.UserId))
             {
                 LibraryHelpers.AddMediaPath(request.Name, request.Path, null, _appPaths);
             }
             else
             {
-                var user = kernel.GetUserById(new Guid(request.UserId));
+                var user = _userManager.GetUserById(new Guid(request.UserId));
 
                 LibraryHelpers.AddMediaPath(request.Name, request.Path, user, _appPaths);
             }
@@ -261,15 +261,13 @@ namespace MediaBrowser.Api.Library
         /// <param name="request">The request.</param>
         public void Delete(RemoveMediaPath request)
         {
-            var kernel = (Kernel)Kernel;
-
             if (string.IsNullOrEmpty(request.UserId))
             {
                 LibraryHelpers.RemoveMediaPath(request.Name, request.Path, null, _appPaths);
             }
             else
             {
-                var user = kernel.GetUserById(new Guid(request.UserId));
+                var user = _userManager.GetUserById(new Guid(request.UserId));
 
                 LibraryHelpers.RemoveMediaPath(request.Name, request.Path, user, _appPaths);
             }

+ 10 - 2
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -29,6 +29,12 @@ namespace MediaBrowser.Api.Playback
         /// <value>The application paths.</value>
         protected IServerApplicationPaths ApplicationPaths { get; set; }
 
+        /// <summary>
+        /// Gets or sets the user manager.
+        /// </summary>
+        /// <value>The user manager.</value>
+        protected IUserManager UserManager { get; set; }
+        
         /// <summary>
         /// Gets the server kernel.
         /// </summary>
@@ -42,9 +48,11 @@ namespace MediaBrowser.Api.Playback
         /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
         /// </summary>
         /// <param name="appPaths">The app paths.</param>
-        protected BaseStreamingService(IServerApplicationPaths appPaths)
+        /// <param name="userManager">The user manager.</param>
+        protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager)
         {
             ApplicationPaths = appPaths;
+            UserManager = userManager;
         }
 
         /// <summary>
@@ -606,7 +614,7 @@ namespace MediaBrowser.Api.Playback
         /// <returns>StreamState.</returns>
         protected StreamState GetState(StreamRequest request)
         {
-            var item = DtoBuilder.GetItemByClientId(request.Id);
+            var item = DtoBuilder.GetItemByClientId(request.Id, UserManager);
 
             var media = (IHasMediaStreams)item;
 

+ 3 - 2
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Library;
 using ServiceStack.ServiceHost;
 using System.Collections.Generic;
 
@@ -27,8 +28,8 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// Initializes a new instance of the <see cref="BaseProgressiveStreamingService" /> class.
         /// </summary>
         /// <param name="appPaths">The app paths.</param>
-        public AudioService(IServerApplicationPaths appPaths)
-            : base(appPaths)
+        public AudioService(IServerApplicationPaths appPaths, IUserManager userManager)
+            : base(appPaths, userManager)
         {
         }
 

+ 3 - 2
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
 using System.IO;
 using System.Threading.Tasks;
@@ -17,8 +18,8 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// Initializes a new instance of the <see cref="BaseProgressiveStreamingService" /> class.
         /// </summary>
         /// <param name="appPaths">The app paths.</param>
-        protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths)
-            : base(appPaths)
+        protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager)
+            : base(appPaths, userManager)
         {
         }
 

+ 3 - 2
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using System;
+using MediaBrowser.Controller.Library;
 using ServiceStack.ServiceHost;
 
 namespace MediaBrowser.Api.Playback.Progressive
@@ -33,8 +34,8 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// Initializes a new instance of the <see cref="BaseProgressiveStreamingService" /> class.
         /// </summary>
         /// <param name="appPaths">The app paths.</param>
-        public VideoService(IServerApplicationPaths appPaths)
-            : base(appPaths)
+        public VideoService(IServerApplicationPaths appPaths, IUserManager userManager)
+            : base(appPaths, userManager)
         {
         }
 

+ 1 - 4
MediaBrowser.Api/Streaming/BaseStreamingHandler.cs

@@ -221,10 +221,7 @@ namespace MediaBrowser.Api.Streaming
         /// <value>The library item.</value>
         protected TBaseItemType LibraryItem
         {
-            get
-            {
-                return _libraryItem ?? (_libraryItem = (TBaseItemType)DtoBuilder.GetItemByClientId(QueryString["id"]));
-            }
+            get { return _libraryItem; }
         }
 
         /// <summary>

+ 16 - 4
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -20,6 +20,20 @@ namespace MediaBrowser.Api.UserLibrary
     public abstract class BaseItemsByNameService<TItemType> : BaseRestService
         where TItemType : BaseItem
     {
+        /// <summary>
+        /// The _user manager
+        /// </summary>
+        protected readonly IUserManager UserManager;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BaseItemsByNameService{TItemType}" /> class.
+        /// </summary>
+        /// <param name="userManager">The user manager.</param>
+        protected BaseItemsByNameService(IUserManager userManager)
+        {
+            UserManager = userManager;
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>
@@ -27,11 +41,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>Task{ItemsResult}.</returns>
         protected async Task<ItemsResult> GetResult(GetItemsByName request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.UserId);
+            var user = UserManager.GetUserById(request.UserId);
 
-            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, user.Id);
+            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, UserManager, user.Id);
 
             IEnumerable<BaseItem> items;
 

+ 5 - 0
MediaBrowser.Api/UserLibrary/GenresService.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
 using ServiceStack.ServiceHost;
 using System;
 using System.Collections.Generic;
@@ -23,6 +24,10 @@ namespace MediaBrowser.Api.UserLibrary
     /// </summary>
     public class GenresService : BaseItemsByNameService<Genre>
     {
+        public GenresService(IUserManager userManager) : base(userManager)
+        {
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>

+ 16 - 4
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -145,6 +145,20 @@ namespace MediaBrowser.Api.UserLibrary
     /// </summary>
     public class ItemsService : BaseRestService
     {
+        /// <summary>
+        /// The _user manager
+        /// </summary>
+        private readonly IUserManager _userManager;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ItemsService" /> class.
+        /// </summary>
+        /// <param name="userManager">The user manager.</param>
+        public ItemsService(IUserManager userManager)
+        {
+            _userManager = userManager;
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>
@@ -164,9 +178,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>Task{ItemsResult}.</returns>
         private async Task<ItemsResult> GetItems(GetItems request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(request.UserId);
 
             var items = GetItemsToSerialize(request, user);
 
@@ -209,7 +221,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <exception cref="System.InvalidOperationException"></exception>
         private IEnumerable<BaseItem> GetItemsToSerialize(GetItems request, User user)
         {
-            var item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.ParentId, user.Id);
+            var item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.ParentId, _userManager, user.Id);
 
             // Default list type = children
 

+ 5 - 0
MediaBrowser.Api/UserLibrary/PersonsService.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
 using ServiceStack.ServiceHost;
 using System;
 using System.Collections.Generic;
@@ -28,6 +29,10 @@ namespace MediaBrowser.Api.UserLibrary
     /// </summary>
     public class PersonsService : BaseItemsByNameService<Person>
     {
+        public PersonsService(IUserManager userManager) : base(userManager)
+        {
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>

+ 6 - 0
MediaBrowser.Api/UserLibrary/StudiosService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
 using ServiceStack.ServiceHost;
 using System;
 using System.Collections.Generic;
@@ -22,6 +23,11 @@ namespace MediaBrowser.Api.UserLibrary
     /// </summary>
     public class StudiosService : BaseItemsByNameService<Studio>
     {
+        public StudiosService(IUserManager userManager)
+            : base(userManager)
+        {
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>

+ 42 - 60
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -312,12 +312,17 @@ namespace MediaBrowser.Api.UserLibrary
         /// </summary>
         private readonly IJsonSerializer _jsonSerializer;
 
+        /// <summary>
+        /// The _user manager
+        /// </summary>
+        private readonly IUserManager _userManager;
+        
         /// <summary>
         /// Initializes a new instance of the <see cref="UserLibraryService" /> class.
         /// </summary>
         /// <param name="jsonSerializer">The json serializer.</param>
         /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
-        public UserLibraryService(IJsonSerializer jsonSerializer)
+        public UserLibraryService(IJsonSerializer jsonSerializer, IUserManager userManager)
             : base()
         {
             if (jsonSerializer == null)
@@ -326,6 +331,7 @@ namespace MediaBrowser.Api.UserLibrary
             }
 
             _jsonSerializer = jsonSerializer;
+            _userManager = userManager;
         }
 
         /// <summary>
@@ -335,11 +341,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         public object Get(GetSpecialFeatures request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(request.UserId);
 
-            var item = DtoBuilder.GetItemByClientId(request.Id, user.Id);
+            var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
@@ -360,11 +364,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         public object Get(GetLocalTrailers request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(request.UserId);
 
-            var item = DtoBuilder.GetItemByClientId(request.Id, user.Id);
+            var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
@@ -383,11 +385,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         public object Get(GetItem request)
         {
-            var kernel = (Kernel)Kernel;
+            var user = _userManager.GetUserById(request.UserId);
 
-            var user = kernel.GetUserById(request.UserId);
-
-            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, user.Id);
+            var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
@@ -408,9 +408,9 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var kernel = (Kernel)Kernel;
 
-            var user = kernel.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(request.UserId);
 
-            var item = DtoBuilder.GetItemByClientId(request.Id, user.Id);
+            var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
             var result = kernel.IntroProviders.SelectMany(i => i.GetIntros(item, user));
 
@@ -431,9 +431,9 @@ namespace MediaBrowser.Api.UserLibrary
 
             var kernel = (Kernel)Kernel;
 
-            var user = kernel.GetUserById(userId);
+            var user = _userManager.GetUserById(userId);
 
-            var item = (Folder)DtoBuilder.GetItemByClientId(itemId, user.Id);
+            var item = (Folder)DtoBuilder.GetItemByClientId(itemId, _userManager, user.Id);
 
             var displayPreferences = _jsonSerializer.DeserializeFromStream<DisplayPreferences>(request.RequestStream);
 
@@ -448,11 +448,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         public void Post(MarkFavoriteItem request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(request.UserId);
 
-            var item = (Folder)DtoBuilder.GetItemByClientId(request.Id, user.Id);
+            var item = (Folder)DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
             // Get the user data for this item
             var data = item.GetUserData(user, true);
@@ -460,7 +458,7 @@ namespace MediaBrowser.Api.UserLibrary
             // Set favorite status
             data.IsFavorite = true;
 
-            var task = kernel.UserDataManager.SaveUserDataForItem(user, item, data);
+            var task = _userManager.SaveUserDataForItem(user, item, data);
 
             Task.WaitAll(task);
         }
@@ -471,11 +469,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         public void Delete(UnmarkFavoriteItem request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(request.UserId);
 
-            var item = (Folder)DtoBuilder.GetItemByClientId(request.Id, user.Id);
+            var item = (Folder)DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
             // Get the user data for this item
             var data = item.GetUserData(user, true);
@@ -483,7 +479,7 @@ namespace MediaBrowser.Api.UserLibrary
             // Set favorite status
             data.IsFavorite = false;
 
-            var task = kernel.UserDataManager.SaveUserDataForItem(user, item, data);
+            var task = _userManager.SaveUserDataForItem(user, item, data);
 
             Task.WaitAll(task);
         }
@@ -494,18 +490,16 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         public void Delete(DeleteUserItemRating request)
         {
-            var kernel = (Kernel)Kernel;
+            var user = _userManager.GetUserById(request.UserId);
 
-            var user = kernel.GetUserById(request.UserId);
-
-            var item = (Folder)DtoBuilder.GetItemByClientId(request.Id, user.Id);
+            var item = (Folder)DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
             // Get the user data for this item
             var data = item.GetUserData(user, true);
 
             data.Rating = null;
 
-            var task = kernel.UserDataManager.SaveUserDataForItem(user, item, data);
+            var task = _userManager.SaveUserDataForItem(user, item, data);
 
             Task.WaitAll(task);
         }
@@ -516,18 +510,16 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         public void Post(UpdateUserItemRating request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(request.UserId);
 
-            var item = (Folder)DtoBuilder.GetItemByClientId(request.Id, user.Id);
+            var item = (Folder)DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
             // Get the user data for this item
             var data = item.GetUserData(user, true);
 
             data.Likes = request.Likes;
 
-            var task = kernel.UserDataManager.SaveUserDataForItem(user, item, data);
+            var task = _userManager.SaveUserDataForItem(user, item, data);
 
             Task.WaitAll(task);
         }
@@ -538,9 +530,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         public void Post(MarkPlayedItem request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(request.UserId);
 
             var task = UpdatePlayedStatus(user, request.Id, true);
 
@@ -553,13 +543,11 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         public void Post(OnPlaybackStart request)
         {
-            var kernel = (Kernel)Kernel;
+            var user = _userManager.GetUserById(request.UserId);
 
-            var user = kernel.GetUserById(request.UserId);
+            var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
-            var item = DtoBuilder.GetItemByClientId(request.Id, user.Id);
-            
-            kernel.UserDataManager.OnPlaybackStart(user, item, ClientType.Other, string.Empty);
+            _userManager.OnPlaybackStart(user, item, ClientType.Other, string.Empty);
         }
 
         /// <summary>
@@ -568,13 +556,11 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         public void Post(OnPlaybackProgress request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(request.UserId);
 
-            var item = DtoBuilder.GetItemByClientId(request.Id, user.Id);
+            var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
-            var task = kernel.UserDataManager.OnPlaybackProgress(user, item, request.PositionTicks, ClientType.Other, string.Empty);
+            var task = _userManager.OnPlaybackProgress(user, item, request.PositionTicks, ClientType.Other, string.Empty);
 
             Task.WaitAll(task);
         }
@@ -585,13 +571,11 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         public void Post(OnPlaybackStopped request)
         {
-            var kernel = (Kernel)Kernel;
+            var user = _userManager.GetUserById(request.UserId);
 
-            var user = kernel.GetUserById(request.UserId);
+            var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, user.Id);
 
-            var item = DtoBuilder.GetItemByClientId(request.Id, user.Id);
-
-            var task = kernel.UserDataManager.OnPlaybackStopped(user, item, request.PositionTicks, ClientType.Other, string.Empty);
+            var task = _userManager.OnPlaybackStopped(user, item, request.PositionTicks, ClientType.Other, string.Empty);
 
             Task.WaitAll(task);
         }
@@ -602,9 +586,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         public void Delete(MarkUnplayedItem request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.UserId);
+            var user = _userManager.GetUserById(request.UserId);
 
             var task = UpdatePlayedStatus(user, request.Id, false);
 
@@ -620,9 +602,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>Task.</returns>
         private Task UpdatePlayedStatus(User user, string itemId, bool wasPlayed)
         {
-            var item = DtoBuilder.GetItemByClientId(itemId, user.Id);
+            var item = DtoBuilder.GetItemByClientId(itemId, _userManager, user.Id);
 
-            return item.SetPlayedStatus(user, wasPlayed);
+            return item.SetPlayedStatus(user, wasPlayed, _userManager);
         }
     }
 }

+ 5 - 0
MediaBrowser.Api/UserLibrary/YearsService.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
 using ServiceStack.ServiceHost;
 using System;
 using System.Collections.Generic;
@@ -29,6 +30,10 @@ namespace MediaBrowser.Api.UserLibrary
         /// </summary>
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
+        public YearsService(IUserManager userManager) : base(userManager)
+        {
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>

+ 20 - 24
MediaBrowser.Api/UserService.cs

@@ -145,13 +145,18 @@ namespace MediaBrowser.Api
         /// </summary>
         private readonly IJsonSerializer _jsonSerializer;
 
+        /// <summary>
+        /// The _user manager
+        /// </summary>
+        private readonly IUserManager _userManager;
+        
         /// <summary>
         /// Initializes a new instance of the <see cref="UserService" /> class.
         /// </summary>
         /// <param name="xmlSerializer">The XML serializer.</param>
         /// <param name="jsonSerializer">The json serializer.</param>
         /// <exception cref="System.ArgumentNullException">xmlSerializer</exception>
-        public UserService(IXmlSerializer xmlSerializer, IJsonSerializer jsonSerializer)
+        public UserService(IXmlSerializer xmlSerializer, IJsonSerializer jsonSerializer, IUserManager userManager)
             : base()
         {
             if (jsonSerializer == null)
@@ -166,6 +171,7 @@ namespace MediaBrowser.Api
 
             _jsonSerializer = jsonSerializer;
             _xmlSerializer = xmlSerializer;
+            _userManager = userManager;
         }
 
         /// <summary>
@@ -179,7 +185,7 @@ namespace MediaBrowser.Api
 
             var dtoBuilder = new DtoBuilder(Logger);
 
-            var result = kernel.Users.OrderBy(u => u.Name).Select(dtoBuilder.GetDtoUser).ToList();
+            var result = _userManager.Users.OrderBy(u => u.Name).Select(dtoBuilder.GetDtoUser).ToList();
 
             return ToOptimizedResult(result);
         }
@@ -191,9 +197,7 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         public object Get(GetUser request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.Id);
+            var user = _userManager.GetUserById(request.Id);
 
             if (user == null)
             {
@@ -211,16 +215,14 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         public void Delete(DeleteUser request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.Id);
+            var user = _userManager.GetUserById(request.Id);
 
             if (user == null)
             {
                 throw new ResourceNotFoundException("User not found");
             }
 
-            var task = kernel.UserManager.DeleteUser(user);
+            var task = _userManager.DeleteUser(user);
 
             Task.WaitAll(task);
         }
@@ -231,16 +233,14 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         public void Post(AuthenticateUser request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.Id);
+            var user = _userManager.GetUserById(request.Id);
 
             if (user == null)
             {
                 throw new ResourceNotFoundException("User not found");
             }
 
-            var success = kernel.UserManager.AuthenticateUser(user, request.Password).Result;
+            var success = _userManager.AuthenticateUser(user, request.Password).Result;
 
             if (!success)
             {
@@ -255,9 +255,7 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         public void Post(UpdateUserPassword request)
         {
-            var kernel = (Kernel)Kernel;
-
-            var user = kernel.GetUserById(request.Id);
+            var user = _userManager.GetUserById(request.Id);
 
             if (user == null)
             {
@@ -266,20 +264,20 @@ namespace MediaBrowser.Api
 
             if (request.ResetPassword)
             {
-                var task = user.ResetPassword();
+                var task = user.ResetPassword(_userManager);
 
                 Task.WaitAll(task);
             }
             else
             {
-                var success = kernel.UserManager.AuthenticateUser(user, request.CurrentPassword).Result;
+                var success = _userManager.AuthenticateUser(user, request.CurrentPassword).Result;
 
                 if (!success)
                 {
                     throw new ResourceNotFoundException("Invalid user or password entered.");
                 }
 
-                var task = user.ChangePassword(request.NewPassword);
+                var task = user.ChangePassword(request.NewPassword, _userManager);
 
                 Task.WaitAll(task);
             }
@@ -296,13 +294,11 @@ namespace MediaBrowser.Api
             var pathInfo = PathInfo.Parse(Request.PathInfo);
             var id = new Guid(pathInfo.GetArgumentValue<string>(1));
 
-            var kernel = (Kernel)Kernel;
-
             var dtoUser = _jsonSerializer.DeserializeFromStream<UserDto>(request.RequestStream);
 
-            var user = kernel.GetUserById(id);
+            var user = _userManager.GetUserById(id);
 
-            var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? kernel.UserManager.UpdateUser(user) : kernel.UserManager.RenameUser(user, dtoUser.Name);
+            var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name);
 
             Task.WaitAll(task);
 
@@ -320,7 +316,7 @@ namespace MediaBrowser.Api
 
             var dtoUser = _jsonSerializer.DeserializeFromStream<UserDto>(request.RequestStream);
 
-            var newUser = kernel.UserManager.CreateUser(dtoUser.Name).Result;
+            var newUser = _userManager.CreateUser(dtoUser.Name).Result;
 
             var result = new DtoBuilder(Logger).GetDtoUser(newUser);
 

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

@@ -1205,7 +1205,7 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="wasPlayed">if set to <c>true</c> [was played].</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
-        public virtual Task SetPlayedStatus(User user, bool wasPlayed)
+        public virtual Task SetPlayedStatus(User user, bool wasPlayed, IUserManager userManager)
         {
             if (user == null)
             {
@@ -1235,7 +1235,7 @@ namespace MediaBrowser.Controller.Entities
 
             data.Played = wasPlayed;
 
-            return Kernel.Instance.UserDataManager.SaveUserDataForItem(user, this, data);
+            return userManager.SaveUserDataForItem(user, this, data);
         }
 
         /// <summary>

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

@@ -962,12 +962,12 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="user">The user.</param>
         /// <param name="wasPlayed">if set to <c>true</c> [was played].</param>
         /// <returns>Task.</returns>
-        public override async Task SetPlayedStatus(User user, bool wasPlayed)
+        public override async Task SetPlayedStatus(User user, bool wasPlayed, IUserManager userManager)
         {
-            await base.SetPlayedStatus(user, wasPlayed).ConfigureAwait(false);
+            await base.SetPlayedStatus(user, wasPlayed, userManager).ConfigureAwait(false);
 
             // Now sweep through recursively and update status
-            var tasks = GetChildren(user).Select(c => c.SetPlayedStatus(user, wasPlayed));
+            var tasks = GetChildren(user).Select(c => c.SetPlayedStatus(user, wasPlayed, userManager));
 
             await Task.WhenAll(tasks).ConfigureAwait(false);
         }

+ 9 - 6
MediaBrowser.Controller/Entities/User.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Configuration;
 using System;
 using System.IO;
@@ -16,6 +17,8 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public class User : BaseItem
     {
+        internal static IUserManager UserManager { get; set; }
+
         /// <summary>
         /// The _root folder path
         /// </summary>
@@ -236,7 +239,7 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="newName">The new name.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
-        internal Task Rename(string newName)
+        public Task Rename(string newName)
         {
             if (string.IsNullOrEmpty(newName))
             {
@@ -364,7 +367,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 cancellationToken.ThrowIfCancellationRequested();
 
-                await Kernel.Instance.UserManager.UpdateUser(this).ConfigureAwait(false);
+                await UserManager.UpdateUser(this).ConfigureAwait(false);
             }
 
             return changed;
@@ -425,9 +428,9 @@ namespace MediaBrowser.Controller.Entities
         /// Resets the password by clearing it.
         /// </summary>
         /// <returns>Task.</returns>
-        public Task ResetPassword()
+        public Task ResetPassword(IUserManager userManager)
         {
-            return ChangePassword(string.Empty);
+            return ChangePassword(string.Empty, userManager);
         }
 
         /// <summary>
@@ -435,11 +438,11 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <param name="newPassword">The new password.</param>
         /// <returns>Task.</returns>
-        public Task ChangePassword(string newPassword)
+        public Task ChangePassword(string newPassword, IUserManager userManager)
         {
             Password = string.IsNullOrEmpty(newPassword) ? string.Empty : newPassword.GetMD5().ToString();
 
-            return Kernel.Instance.UserManager.UpdateUser(this);
+            return userManager.UpdateUser(this);
         }
     }
 }

+ 7 - 72
MediaBrowser.Controller/Kernel.cs

@@ -1,8 +1,5 @@
 using MediaBrowser.Common.Kernel;
-using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.ScheduledTasks;
-using MediaBrowser.Common.Security;
-using MediaBrowser.Common.Updates;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
@@ -51,12 +48,6 @@ namespace MediaBrowser.Controller
         /// <value>The image manager.</value>
         public ImageManager ImageManager { get; private set; }
 
-        /// <summary>
-        /// Gets the user manager.
-        /// </summary>
-        /// <value>The user manager.</value>
-        public UserManager UserManager { get; private set; }
-
         /// <summary>
         /// Gets the FFMPEG controller.
         /// </summary>
@@ -81,47 +72,6 @@ namespace MediaBrowser.Controller
         /// <value>The provider manager.</value>
         public ProviderManager ProviderManager { get; private set; }
 
-        /// <summary>
-        /// Gets the user data manager.
-        /// </summary>
-        /// <value>The user data manager.</value>
-        public UserDataManager UserDataManager { get; private set; }
-
-        /// <summary>
-        /// The _users
-        /// </summary>
-        private IEnumerable<User> _users;
-        /// <summary>
-        /// The _user lock
-        /// </summary>
-        private object _usersSyncLock = new object();
-        /// <summary>
-        /// The _users initialized
-        /// </summary>
-        private bool _usersInitialized;
-        /// <summary>
-        /// Gets the users.
-        /// </summary>
-        /// <value>The users.</value>
-        public IEnumerable<User> Users
-        {
-            get
-            {
-                // Call ToList to exhaust the stream because we'll be iterating over this multiple times
-                LazyInitializer.EnsureInitialized(ref _users, ref _usersInitialized, ref _usersSyncLock, UserManager.LoadUsers);
-                return _users;
-            }
-            internal set
-            {
-                _users = value;
-
-                if (value == null)
-                {
-                    _usersInitialized = false;
-                }
-            }
-        }
-
         /// <summary>
         /// The _root folder
         /// </summary>
@@ -304,13 +254,14 @@ namespace MediaBrowser.Controller
         /// </summary>
         protected override void FindParts()
         {
+            // For now there's no real way to inject this properly
+            User.UserManager = ApplicationHost.Resolve<IUserManager>();
+
             InstallationManager = (InstallationManager)ApplicationHost.CreateInstance(typeof(InstallationManager));
             FFMpegManager = (FFMpegManager)ApplicationHost.CreateInstance(typeof(FFMpegManager));
             LibraryManager = (LibraryManager)ApplicationHost.CreateInstance(typeof(LibraryManager));
-            UserManager = (UserManager)ApplicationHost.CreateInstance(typeof(UserManager));
             ImageManager = (ImageManager)ApplicationHost.CreateInstance(typeof(ImageManager));
             ProviderManager = (ProviderManager)ApplicationHost.CreateInstance(typeof(ProviderManager));
-            UserDataManager = (UserDataManager)ApplicationHost.CreateInstance(typeof(UserDataManager));
             SecurityManager = (PluginSecurityManager)ApplicationHost.CreateInstance(typeof(PluginSecurityManager));
             
             base.FindParts();
@@ -337,7 +288,6 @@ namespace MediaBrowser.Controller
         protected override async Task ReloadInternal()
         {
             // Reset these so that they can be lazy loaded again
-            Users = null;
             RootFolder = null;
 
             await base.ReloadInternal().ConfigureAwait(false);
@@ -346,7 +296,7 @@ namespace MediaBrowser.Controller
 
             ReloadFileSystemManager();
 
-            await UserManager.RefreshUsersMetadata(CancellationToken.None).ConfigureAwait(false);
+            await ApplicationHost.Resolve<IUserManager>().RefreshUsersMetadata(CancellationToken.None).ConfigureAwait(false);
         }
 
         /// <summary>
@@ -453,30 +403,15 @@ namespace MediaBrowser.Controller
             FileSystemManager.StartWatchers();
         }
 
-        /// <summary>
-        /// Gets a User by Id
-        /// </summary>
-        /// <param name="id">The id.</param>
-        /// <returns>User.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public User GetUserById(Guid id)
-        {
-            if (id == Guid.Empty)
-            {
-                throw new ArgumentNullException();
-            }
-
-            return Users.FirstOrDefault(u => u.Id == id);
-        }
-
         /// <summary>
         /// Finds a library item by Id and UserId.
         /// </summary>
         /// <param name="id">The id.</param>
         /// <param name="userId">The user id.</param>
+        /// <param name="userManager">The user manager.</param>
         /// <returns>BaseItem.</returns>
         /// <exception cref="System.ArgumentNullException">id</exception>
-        public BaseItem GetItemById(Guid id, Guid userId)
+        public BaseItem GetItemById(Guid id, Guid userId, IUserManager userManager)
         {
             if (id == Guid.Empty)
             {
@@ -488,7 +423,7 @@ namespace MediaBrowser.Controller
                 throw new ArgumentNullException("userId");
             }
 
-            var user = GetUserById(userId);
+            var user = userManager.GetUserById(userId);
             var userRoot = user.RootFolder;
 
             return userRoot.FindItemById(id, user);

+ 9 - 9
MediaBrowser.Controller/Library/DtoBuilder.cs

@@ -823,7 +823,7 @@ namespace MediaBrowser.Controller.Library
         /// <param name="id">The id.</param>
         /// <param name="userId">The user id.</param>
         /// <returns>BaseItem.</returns>
-        public static BaseItem GetItemByClientId(string id, Guid? userId = null)
+        public static BaseItem GetItemByClientId(string id, IUserManager userManager, Guid? userId = null)
         {
             var isIdEmpty = string.IsNullOrEmpty(id);
 
@@ -835,7 +835,7 @@ namespace MediaBrowser.Controller.Library
             {
                 if (userId.HasValue)
                 {
-                    return GetIndexFolder(id, userId.Value);
+                    return GetIndexFolder(id, userId.Value, userManager);
                 }
             }
 
@@ -844,8 +844,8 @@ namespace MediaBrowser.Controller.Library
             if (userId.HasValue)
             {
                 item = isIdEmpty
-                           ? Kernel.Instance.GetUserById(userId.Value).RootFolder
-                           : Kernel.Instance.GetItemById(new Guid(id), userId.Value);
+                           ? userManager.GetUserById(userId.Value).RootFolder
+                           : Kernel.Instance.GetItemById(new Guid(id), userId.Value, userManager);
             }
             else if (!isIndexFolder)
             {
@@ -855,9 +855,9 @@ namespace MediaBrowser.Controller.Library
             // If we still don't find it, look within individual user views
             if (item == null && !userId.HasValue)
             {
-                foreach (var user in Kernel.Instance.Users)
+                foreach (var user in userManager.Users)
                 {
-                    item = GetItemByClientId(id, user.Id);
+                    item = GetItemByClientId(id, userManager, user.Id);
 
                     if (item != null)
                     {
@@ -875,9 +875,9 @@ namespace MediaBrowser.Controller.Library
         /// <param name="id">The id.</param>
         /// <param name="userId">The user id.</param>
         /// <returns>BaseItem.</returns>
-        private static BaseItem GetIndexFolder(string id, Guid userId)
+        private static BaseItem GetIndexFolder(string id, Guid userId, IUserManager userManager)
         {
-            var user = Kernel.Instance.GetUserById(userId);
+            var user = userManager.GetUserById(userId);
 
             var stringSeparators = new[] { IndexFolderDelimeter };
 
@@ -885,7 +885,7 @@ namespace MediaBrowser.Controller.Library
             var values = id.Split(stringSeparators, StringSplitOptions.None).ToList();
 
             // Get the top folder normally using the first id
-            var folder = GetItemByClientId(values[0], userId) as Folder;
+            var folder = GetItemByClientId(values[0], userManager, userId) as Folder;
 
             values.RemoveAt(0);
 

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

@@ -0,0 +1,169 @@
+using MediaBrowser.Common.Events;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Connectivity;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Library
+{
+    public interface IUserManager
+    {
+        /// <summary>
+        /// Gets the users.
+        /// </summary>
+        /// <value>The users.</value>
+        IEnumerable<User> Users { get; }
+
+        /// <summary>
+        /// Gets the active connections.
+        /// </summary>
+        /// <value>The active connections.</value>
+        IEnumerable<ClientConnectionInfo> ConnectedUsers { get; }
+
+        /// <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>
+        /// Occurs when [user updated].
+        /// </summary>
+        event EventHandler<GenericEventArgs<User>> UserUpdated;
+
+        /// <summary>
+        /// Occurs when [user deleted].
+        /// </summary>
+        event EventHandler<GenericEventArgs<User>> UserDeleted;
+
+        /// <summary>
+        /// Gets a User by Id
+        /// </summary>
+        /// <param name="id">The id.</param>
+        /// <returns>User.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        User GetUserById(Guid id);
+
+        /// <summary>
+        /// Authenticates a User and returns a result indicating whether or not it succeeded
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="password">The password.</param>
+        /// <returns>Task{System.Boolean}.</returns>
+        /// <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="deviceName">Name of the device.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException">user</exception>
+        Task LogUserActivity(User user, ClientType clientType, string deviceName);
+
+        /// <summary>
+        /// Loads the users from the repository
+        /// </summary>
+        /// <returns>IEnumerable{User}.</returns>
+        IEnumerable<User> LoadUsers();
+
+        /// <summary>
+        /// Refreshes metadata for each user
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <param name="force">if set to <c>true</c> [force].</param>
+        /// <returns>Task.</returns>
+        Task RefreshUsersMetadata(CancellationToken cancellationToken, bool force = false);
+
+        /// <summary>
+        /// Renames the user.
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="newName">The new name.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException">user</exception>
+        /// <exception cref="System.ArgumentException"></exception>
+        Task RenameUser(User user, string newName);
+
+        /// <summary>
+        /// Updates the user.
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <exception cref="System.ArgumentNullException">user</exception>
+        /// <exception cref="System.ArgumentException"></exception>
+        Task UpdateUser(User user);
+
+        /// <summary>
+        /// Creates the user.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        /// <returns>User.</returns>
+        /// <exception cref="System.ArgumentNullException">name</exception>
+        /// <exception cref="System.ArgumentException"></exception>
+        Task<User> CreateUser(string name);
+
+        /// <summary>
+        /// Deletes the user.
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException">user</exception>
+        /// <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="deviceName">Name of the device.</param>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        void OnPlaybackStart(User user, BaseItem item, ClientType clientType, 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="deviceName">Name of the device.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        Task OnPlaybackProgress(User user, BaseItem item, long? positionTicks, ClientType clientType, 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="deviceName">Name of the device.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        Task OnPlaybackStopped(User user, BaseItem item, long? positionTicks, ClientType clientType, string deviceName);
+
+        /// <summary>
+        /// Saves user data for an item
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="item">The item.</param>
+        /// <param name="data">The data.</param>
+        Task SaveUserDataForItem(User user, BaseItem item, UserItemData data);
+    }
+}

+ 6 - 3
MediaBrowser.Controller/Library/LibraryManager.cs

@@ -64,6 +64,8 @@ namespace MediaBrowser.Controller.Library
         /// </summary>
         private readonly ITaskManager _taskManager;
 
+        private readonly IUserManager _userManager;
+        
         /// <summary>
         /// Gets or sets the kernel.
         /// </summary>
@@ -76,11 +78,12 @@ namespace MediaBrowser.Controller.Library
         /// <param name="kernel">The kernel.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="taskManager">The task manager.</param>
-        public LibraryManager(Kernel kernel, ILogger logger, ITaskManager taskManager)
+        public LibraryManager(Kernel kernel, ILogger logger, ITaskManager taskManager, IUserManager userManager)
         {
             Kernel = kernel;
             _logger = logger;
             _taskManager = taskManager;
+            _userManager = userManager;
 
             kernel.ConfigurationUpdated += kernel_ConfigurationUpdated;
         }
@@ -490,13 +493,13 @@ namespace MediaBrowser.Controller.Library
             await Kernel.RootFolder.ValidateChildren(new Progress<double> { }, cancellationToken, recursive: false);
 
             // Validate only the collection folders for each user, just to make them available as quickly as possible
-            var userCollectionFolderTasks = Kernel.Users.AsParallel().Select(user => user.ValidateCollectionFolders(new Progress<double> { }, cancellationToken));
+            var userCollectionFolderTasks = _userManager.Users.AsParallel().Select(user => user.ValidateCollectionFolders(new Progress<double> { }, cancellationToken));
             await Task.WhenAll(userCollectionFolderTasks).ConfigureAwait(false);
 
             // Now validate the entire media library
             await Kernel.RootFolder.ValidateChildren(progress, cancellationToken, recursive: true).ConfigureAwait(false);
 
-            foreach (var user in Kernel.Users)
+            foreach (var user in _userManager.Users)
             {
                 await user.ValidateMediaLibrary(new Progress<double> { }, cancellationToken).ConfigureAwait(false);
             }

+ 0 - 226
MediaBrowser.Controller/Library/UserDataManager.cs

@@ -1,226 +0,0 @@
-using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Kernel;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Connectivity;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Library
-{
-    /// <summary>
-    /// Class UserDataManager
-    /// </summary>
-    public class UserDataManager : BaseManager<Kernel>
-    {
-        #region Events
-        /// <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;
-        #endregion
-
-        /// <summary>
-        /// The _logger
-        /// </summary>
-        private readonly ILogger _logger;
-        
-        /// <summary>
-        /// Initializes a new instance of the <see cref="UserDataManager" /> class.
-        /// </summary>
-        /// <param name="kernel">The kernel.</param>
-        /// <param name="logger">The logger.</param>
-        public UserDataManager(Kernel kernel, ILogger logger)
-            : base(kernel)
-        {
-            _logger = logger;
-        }
-
-        /// <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="deviceName">Name of the device.</param>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public void OnPlaybackStart(User user, BaseItem item, ClientType clientType, string deviceName)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException();
-            }
-            if (item == null)
-            {
-                throw new ArgumentNullException();
-            }
-
-            Kernel.UserManager.UpdateNowPlayingItemId(user, clientType, deviceName, item);
-
-            // Nothing to save here
-            // Fire events to inform plugins
-            EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
-            {
-                Argument = 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="deviceName">Name of the device.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public async Task OnPlaybackProgress(User user, BaseItem item, long? positionTicks, ClientType clientType, string deviceName)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException();
-            }
-            if (item == null)
-            {
-                throw new ArgumentNullException();
-            }
-
-            Kernel.UserManager.UpdateNowPlayingItemId(user, clientType, deviceName, item, positionTicks);
-
-            if (positionTicks.HasValue)
-            {
-                var data = item.GetUserData(user, true);
-
-                UpdatePlayState(item, data, positionTicks.Value, false);
-                await SaveUserDataForItem(user, item, data).ConfigureAwait(false);
-            }
-
-            EventHelper.QueueEventIfNotNull(PlaybackProgress, this, new PlaybackProgressEventArgs
-            {
-                Argument = 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="deviceName">Name of the device.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public async Task OnPlaybackStopped(User user, BaseItem item, long? positionTicks, ClientType clientType, string deviceName)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException();
-            }
-            if (item == null)
-            {
-                throw new ArgumentNullException();
-            }
-
-            Kernel.UserManager.RemoveNowPlayingItemId(user, clientType, deviceName, item);
-            
-            var data = item.GetUserData(user, true);
-
-            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 SaveUserDataForItem(user, item, data).ConfigureAwait(false);
-
-            EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackProgressEventArgs
-            {
-                Argument = 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 < Kernel.Configuration.MinResumePct)
-                {
-                    positionTicks = 0;
-                    incrementPlayCount = false;
-                }
-
-                // If we're at the end, assume completed
-                else if (pctIn > Kernel.Configuration.MaxResumePct || positionTicks >= item.RunTimeTicks.Value)
-                {
-                    positionTicks = 0;
-                    data.Played = true;
-                }
-
-                else
-                {
-                    // Enforce MinResumeDuration
-                    var durationSeconds = TimeSpan.FromTicks(item.RunTimeTicks.Value).TotalSeconds;
-
-                    if (durationSeconds < Kernel.Configuration.MinResumeDurationSeconds)
-                    {
-                        positionTicks = 0;
-                        data.Played = true;
-                    }
-                }
-            }
-
-            data.PlaybackPositionTicks = positionTicks;
-
-            if (incrementPlayCount)
-            {
-                data.PlayCount++;
-                data.LastPlayedDate = DateTime.UtcNow;
-            }
-        }
-
-        /// <summary>
-        /// Saves user data for an item
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="item">The item.</param>
-        /// <param name="data">The data.</param>
-        public Task SaveUserDataForItem(User user, BaseItem item, UserItemData data)
-        {
-            item.AddOrUpdateUserData(user, data);
-
-            return Kernel.UserDataRepository.SaveUserData(item, CancellationToken.None);
-        }
-    }
-}

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

@@ -113,9 +113,8 @@
     <Compile Include="IServerApplicationPaths.cs" />
     <Compile Include="Library\ChildrenChangedEventArgs.cs" />
     <Compile Include="Library\DtoBuilder.cs" />
+    <Compile Include="Library\IUserManager.cs" />
     <Compile Include="Library\Profiler.cs" />
-    <Compile Include="Library\UserDataManager.cs" />
-    <Compile Include="Library\UserManager.cs" />
     <Compile Include="Localization\AURatingsDictionary.cs" />
     <Compile Include="Localization\BaseStrings.cs" />
     <Compile Include="Localization\GBRatingsDictionary.cs" />

+ 269 - 22
MediaBrowser.Controller/Library/UserManager.cs → MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -1,22 +1,23 @@
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Kernel;
+using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Connectivity;
+using MediaBrowser.Model.Logging;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Logging;
 
-namespace MediaBrowser.Controller.Library
+namespace MediaBrowser.Server.Implementations.Library
 {
     /// <summary>
     /// Class UserManager
     /// </summary>
-    public class UserManager : BaseManager<Kernel>
+    public class UserManager : IUserManager
     {
         /// <summary>
         /// The _active connections
@@ -24,20 +25,55 @@ namespace MediaBrowser.Controller.Library
         private readonly ConcurrentBag<ClientConnectionInfo> _activeConnections =
             new ConcurrentBag<ClientConnectionInfo>();
 
+        /// <summary>
+        /// The _users
+        /// </summary>
+        private IEnumerable<User> _users;
+        /// <summary>
+        /// The _user lock
+        /// </summary>
+        private object _usersSyncLock = new object();
+        /// <summary>
+        /// The _users initialized
+        /// </summary>
+        private bool _usersInitialized;
+        /// <summary>
+        /// Gets the users.
+        /// </summary>
+        /// <value>The users.</value>
+        public IEnumerable<User> Users
+        {
+            get
+            {
+                // Call ToList to exhaust the stream because we'll be iterating over this multiple times
+                LazyInitializer.EnsureInitialized(ref _users, ref _usersInitialized, ref _usersSyncLock, LoadUsers);
+                return _users;
+            }
+            internal set
+            {
+                _users = value;
+
+                if (value == null)
+                {
+                    _usersInitialized = false;
+                }
+            }
+        }
+        
         /// <summary>
         /// Gets all connections.
         /// </summary>
         /// <value>All connections.</value>
-        public IEnumerable<ClientConnectionInfo> AllConnections
+        private IEnumerable<ClientConnectionInfo> AllConnections
         {
-            get { return _activeConnections.Where(c => Kernel.GetUserById(c.UserId) != null).OrderByDescending(c => c.LastActivityDate); }
+            get { return _activeConnections.Where(c => GetUserById(c.UserId) != null).OrderByDescending(c => c.LastActivityDate); }
         }
         
         /// <summary>
         /// Gets the active connections.
         /// </summary>
         /// <value>The active connections.</value>
-        public IEnumerable<ClientConnectionInfo> ActiveConnections
+        public IEnumerable<ClientConnectionInfo> ConnectedUsers
         {
             get { return AllConnections.Where(c => (DateTime.UtcNow - c.LastActivityDate).TotalMinutes <= 10); }
         }
@@ -47,17 +83,34 @@ namespace MediaBrowser.Controller.Library
         /// </summary>
         private readonly ILogger _logger;
 
+        private Kernel Kernel { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="UserManager" /> class.
         /// </summary>
         /// <param name="kernel">The kernel.</param>
         /// <param name="logger">The logger.</param>
         public UserManager(Kernel kernel, ILogger logger)
-            : base(kernel)
         {
             _logger = logger;
+            Kernel = kernel;
         }
 
+        #region Events
+        /// <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;
+        #endregion
+
         #region UserUpdated Event
         /// <summary>
         /// Occurs when [user updated].
@@ -68,7 +121,7 @@ namespace MediaBrowser.Controller.Library
         /// Called when [user updated].
         /// </summary>
         /// <param name="user">The user.</param>
-        internal void OnUserUpdated(User user)
+        private void OnUserUpdated(User user)
         {
             EventHelper.QueueEventIfNotNull(UserUpdated, this, new GenericEventArgs<User> { Argument = user }, _logger);
 
@@ -86,7 +139,7 @@ namespace MediaBrowser.Controller.Library
         /// Called when [user deleted].
         /// </summary>
         /// <param name="user">The user.</param>
-        internal void OnUserDeleted(User user)
+        private void OnUserDeleted(User user)
         {
             EventHelper.QueueEventIfNotNull(UserDeleted, this, new GenericEventArgs<User> { Argument = user }, _logger);
 
@@ -95,6 +148,22 @@ namespace MediaBrowser.Controller.Library
         }
         #endregion
 
+        /// <summary>
+        /// Gets a User by Id
+        /// </summary>
+        /// <param name="id">The id.</param>
+        /// <returns>User.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        public User GetUserById(Guid id)
+        {
+            if (id == Guid.Empty)
+            {
+                throw new ArgumentNullException();
+            }
+
+            return Users.FirstOrDefault(u => u.Id == id);
+        }
+        
         /// <summary>
         /// Authenticates a User and returns a result indicating whether or not it succeeded
         /// </summary>
@@ -159,7 +228,7 @@ namespace MediaBrowser.Controller.Library
         /// <param name="deviceName">Name of the device.</param>
         /// <param name="item">The item.</param>
         /// <param name="currentPositionTicks">The current position ticks.</param>
-        public void UpdateNowPlayingItemId(User user, ClientType clientType, string deviceName, BaseItem item, long? currentPositionTicks = null)
+        private void UpdateNowPlayingItemId(User user, ClientType clientType, string deviceName, BaseItem item, long? currentPositionTicks = null)
         {
             var conn = GetConnection(user.Id, clientType, deviceName);
 
@@ -174,7 +243,7 @@ namespace MediaBrowser.Controller.Library
         /// <param name="clientType">Type of the client.</param>
         /// <param name="deviceName">Name of the device.</param>
         /// <param name="item">The item.</param>
-        public void RemoveNowPlayingItemId(User user, ClientType clientType, string deviceName, BaseItem item)
+        private void RemoveNowPlayingItemId(User user, ClientType clientType, string deviceName, BaseItem item)
         {
             var conn = GetConnection(user.Id, clientType, deviceName);
 
@@ -227,7 +296,7 @@ namespace MediaBrowser.Controller.Library
         /// Loads the users from the repository
         /// </summary>
         /// <returns>IEnumerable{User}.</returns>
-        internal IEnumerable<User> LoadUsers()
+        public IEnumerable<User> LoadUsers()
         {
             var users = Kernel.UserRepository.RetrieveAllUsers().ToList();
 
@@ -257,7 +326,7 @@ namespace MediaBrowser.Controller.Library
         /// <returns>Task.</returns>
         public Task RefreshUsersMetadata(CancellationToken cancellationToken, bool force = false)
         {
-            var tasks = Kernel.Users.Select(user => user.RefreshMetadata(cancellationToken, forceRefresh: force)).ToList();
+            var tasks = Users.Select(user => user.RefreshMetadata(cancellationToken, forceRefresh: force)).ToList();
 
             return Task.WhenAll(tasks);
         }
@@ -282,7 +351,7 @@ namespace MediaBrowser.Controller.Library
                 throw new ArgumentNullException("newName");
             }
 
-            if (Kernel.Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)))
+            if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)))
             {
                 throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName));
             }
@@ -310,7 +379,7 @@ namespace MediaBrowser.Controller.Library
                 throw new ArgumentNullException("user");
             }
 
-            if (user.Id == Guid.Empty || !Kernel.Users.Any(u => u.Id.Equals(user.Id)))
+            if (user.Id == Guid.Empty || !Users.Any(u => u.Id.Equals(user.Id)))
             {
                 throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id));
             }
@@ -336,16 +405,16 @@ namespace MediaBrowser.Controller.Library
                 throw new ArgumentNullException("name");
             }
 
-            if (Kernel.Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
+            if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
             {
                 throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name));
             }
 
             var user = InstantiateNewUser(name);
 
-            var list = Kernel.Users.ToList();
+            var list = Users.ToList();
             list.Add(user);
-            Kernel.Users = list;
+            Users = list;
 
             await Kernel.UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
 
@@ -366,12 +435,12 @@ namespace MediaBrowser.Controller.Library
                 throw new ArgumentNullException("user");
             }
 
-            if (Kernel.Users.FirstOrDefault(u => u.Id == user.Id) == null)
+            if (Users.FirstOrDefault(u => u.Id == user.Id) == null)
             {
                 throw new ArgumentException(string.Format("The user cannot be deleted because there is no user with the Name {0} and Id {1}.", user.Name, user.Id));
             }
 
-            if (Kernel.Users.Count() == 1)
+            if (Users.Count() == 1)
             {
                 throw new ArgumentException(string.Format("The user '{0}' be deleted because there must be at least one user in the system.", user.Name));
             }
@@ -381,7 +450,7 @@ namespace MediaBrowser.Controller.Library
             OnUserDeleted(user);
 
             // Force this to be lazy loaded again
-            Kernel.Users = null;
+            Users = null;
         }
 
         /// <summary>
@@ -399,5 +468,183 @@ namespace MediaBrowser.Controller.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="deviceName">Name of the device.</param>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        public void OnPlaybackStart(User user, BaseItem item, ClientType clientType, string deviceName)
+        {
+            if (user == null)
+            {
+                throw new ArgumentNullException();
+            }
+            if (item == null)
+            {
+                throw new ArgumentNullException();
+            }
+
+            UpdateNowPlayingItemId(user, clientType, deviceName, item);
+
+            // Nothing to save here
+            // Fire events to inform plugins
+            EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
+            {
+                Argument = 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="deviceName">Name of the device.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        public async Task OnPlaybackProgress(User user, BaseItem item, long? positionTicks, ClientType clientType, string deviceName)
+        {
+            if (user == null)
+            {
+                throw new ArgumentNullException();
+            }
+            if (item == null)
+            {
+                throw new ArgumentNullException();
+            }
+
+            UpdateNowPlayingItemId(user, clientType, deviceName, item, positionTicks);
+
+            if (positionTicks.HasValue)
+            {
+                var data = item.GetUserData(user, true);
+
+                UpdatePlayState(item, data, positionTicks.Value, false);
+                await SaveUserDataForItem(user, item, data).ConfigureAwait(false);
+            }
+
+            EventHelper.QueueEventIfNotNull(PlaybackProgress, this, new PlaybackProgressEventArgs
+            {
+                Argument = 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="deviceName">Name of the device.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        public async Task OnPlaybackStopped(User user, BaseItem item, long? positionTicks, ClientType clientType, string deviceName)
+        {
+            if (user == null)
+            {
+                throw new ArgumentNullException();
+            }
+            if (item == null)
+            {
+                throw new ArgumentNullException();
+            }
+
+            RemoveNowPlayingItemId(user, clientType, deviceName, item);
+
+            var data = item.GetUserData(user, true);
+
+            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 SaveUserDataForItem(user, item, data).ConfigureAwait(false);
+
+            EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackProgressEventArgs
+            {
+                Argument = 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 < Kernel.Configuration.MinResumePct)
+                {
+                    positionTicks = 0;
+                    incrementPlayCount = false;
+                }
+
+                // If we're at the end, assume completed
+                else if (pctIn > Kernel.Configuration.MaxResumePct || positionTicks >= item.RunTimeTicks.Value)
+                {
+                    positionTicks = 0;
+                    data.Played = true;
+                }
+
+                else
+                {
+                    // Enforce MinResumeDuration
+                    var durationSeconds = TimeSpan.FromTicks(item.RunTimeTicks.Value).TotalSeconds;
+
+                    if (durationSeconds < Kernel.Configuration.MinResumeDurationSeconds)
+                    {
+                        positionTicks = 0;
+                        data.Played = true;
+                    }
+                }
+            }
+
+            data.PlaybackPositionTicks = positionTicks;
+
+            if (incrementPlayCount)
+            {
+                data.PlayCount++;
+                data.LastPlayedDate = DateTime.UtcNow;
+            }
+        }
+
+        /// <summary>
+        /// Saves user data for an item
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="item">The item.</param>
+        /// <param name="data">The data.</param>
+        public Task SaveUserDataForItem(User user, BaseItem item, UserItemData data)
+        {
+            item.AddOrUpdateUserData(user, data);
+
+            return Kernel.UserDataRepository.SaveUserData(item, CancellationToken.None);
+        }
+
     }
 }

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

@@ -50,6 +50,7 @@
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
+    <Compile Include="Library\UserManager.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Reflection\TypeMapper.cs" />
     <Compile Include="ScheduledTasks\Tasks\ChapterImagesTask.cs" />

+ 16 - 16
MediaBrowser.ServerApplication/App.xaml.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.ClickOnce;
 using MediaBrowser.Common.Kernel;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Server.Uninstall;
 using Microsoft.Win32;
@@ -277,21 +278,24 @@ namespace MediaBrowser.ServerApplication
         /// <summary>
         /// Opens the dashboard.
         /// </summary>
-        public static void OpenDashboard()
+        public static void OpenDashboard(User loggedInUser)
         {
-            OpenDashboardPage("dashboard.html");
+            OpenDashboardPage("dashboard.html", loggedInUser);
         }
 
         /// <summary>
         /// Opens the dashboard page.
         /// </summary>
         /// <param name="page">The page.</param>
-        public static void OpenDashboardPage(string page)
+        public static void OpenDashboardPage(string page, User loggedInUser)
         {
             var url = "http://localhost:" + Controller.Kernel.Instance.Configuration.HttpServerPortNumber + "/" +
                       Controller.Kernel.Instance.WebApplicationName + "/dashboard/" + page;
 
-            url = AddAutoLoginToDashboardUrl(url);
+            if (loggedInUser != null)
+            {
+                url = AddAutoLoginToDashboardUrl(url, loggedInUser);
+            }
 
             OpenUrl(url);
         }
@@ -300,21 +304,17 @@ namespace MediaBrowser.ServerApplication
         /// Adds the auto login to dashboard URL.
         /// </summary>
         /// <param name="url">The URL.</param>
+        /// <param name="user">The user.</param>
         /// <returns>System.String.</returns>
-        public static string AddAutoLoginToDashboardUrl(string url)
+        public static string AddAutoLoginToDashboardUrl(string url, User user)
         {
-            var user = Controller.Kernel.Instance.Users.FirstOrDefault(u => u.Configuration.IsAdministrator);
-
-            if (user != null)
+            if (url.IndexOf('?') == -1)
             {
-                if (url.IndexOf('?') == -1)
-                {
-                    url += "?u=" + user.Id;
-                }
-                else
-                {
-                    url += "&u=" + user.Id;
-                }
+                url += "?u=" + user.Id;
+            }
+            else
+            {
+                url += "&u=" + user.Id;
             }
 
             return url;

+ 4 - 0
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -14,6 +14,7 @@ using MediaBrowser.Common.Kernel;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.IsoMounter;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
@@ -22,6 +23,7 @@ using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.Updates;
 using MediaBrowser.Server.Implementations;
+using MediaBrowser.Server.Implementations.Library;
 using MediaBrowser.ServerApplication.Implementations;
 using System;
 using System.Collections.Generic;
@@ -118,6 +120,8 @@ namespace MediaBrowser.ServerApplication
 
             RegisterSingleInstance<IApplicationHost>(this);
 
+            RegisterSingleInstance<IUserManager>(new UserManager(Kernel, Logger));
+
             RegisterSingleInstance(ServerApplicationPaths);
             RegisterSingleInstance<IIsoManager>(new PismoIsoManager(Logger));
             RegisterSingleInstance<IBlurayExaminer>(new BdInfoExaminer());

+ 2 - 2
MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs

@@ -39,14 +39,14 @@ namespace MediaBrowser.ServerApplication
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryExplorer" /> class.
         /// </summary>
-        public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost)
+        public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost, IUserManager userManager)
         {
             _logger = logger;
             _jsonSerializer = jsonSerializer;
 
             InitializeComponent();
             lblVersion.Content = "Version: " + appHost.ApplicationVersion;
-            foreach (var user in Kernel.Instance.Users)
+            foreach (var user in userManager.Users)
                 ddlProfile.Items.Add(user);
             ddlProfile.Items.Insert(0,new User {Name = "Physical"});
             ddlProfile.SelectedIndex = 0;

+ 12 - 17
MediaBrowser.ServerApplication/MainWindow.xaml.cs

@@ -40,11 +40,6 @@ namespace MediaBrowser.ServerApplication
         /// <value>The new item timer.</value>
         private Timer NewItemTimer { get; set; }
 
-        /// <summary>
-        /// The _json serializer
-        /// </summary>
-        private readonly IJsonSerializer _jsonSerializer;
-        
         /// <summary>
         /// The _logger
         /// </summary>
@@ -67,18 +62,13 @@ namespace MediaBrowser.ServerApplication
         /// <param name="logger">The logger.</param>
         /// <param name="appHost">The app host.</param>
         /// <exception cref="System.ArgumentNullException">logger</exception>
-        public MainWindow(IJsonSerializer jsonSerializer, ILogManager logManager, IApplicationHost appHost)
+        public MainWindow(ILogManager logManager, IApplicationHost appHost)
         {
-            if (jsonSerializer == null)
-            {
-                throw new ArgumentNullException("jsonSerializer");
-            }
             if (logManager == null)
             {
                 throw new ArgumentNullException("logManager");
             }
 
-            _jsonSerializer = jsonSerializer;
             _logger = logManager.GetLogger("MainWindow");
             _appHost = appHost;
             _logManager = logManager;
@@ -255,7 +245,9 @@ namespace MediaBrowser.ServerApplication
         /// </summary>
         private void LaunchStartupWizard()
         {
-            App.OpenDashboardPage("wizardStart.html");
+            var user = _appHost.Resolve<IUserManager>().Users.FirstOrDefault(u => u.Configuration.IsAdministrator);
+
+            App.OpenDashboardPage("wizardStart.html", user);
         }
 
         /// <summary>
@@ -265,8 +257,8 @@ namespace MediaBrowser.ServerApplication
         /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
         void cmdApiDocs_Click(object sender, EventArgs e)
         {
-            App.OpenUrl("http://localhost:" + Controller.Kernel.Instance.Configuration.HttpServerPortNumber + "/" +
-                      Controller.Kernel.Instance.WebApplicationName + "/metadata");
+            App.OpenUrl("http://localhost:" + Kernel.Instance.Configuration.HttpServerPortNumber + "/" +
+                      Kernel.Instance.WebApplicationName + "/metadata");
         }
 
         /// <summary>
@@ -301,7 +293,8 @@ namespace MediaBrowser.ServerApplication
         /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
         private void cmOpenExplorer_click(object sender, RoutedEventArgs e)
         {
-            (new LibraryExplorer(_jsonSerializer, _logger, _appHost)).Show();
+            var explorer = (LibraryExplorer)_appHost.CreateInstance(typeof(LibraryExplorer));
+            explorer.Show();
         }
 
         /// <summary>
@@ -311,7 +304,8 @@ namespace MediaBrowser.ServerApplication
         /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
         private void cmOpenDashboard_click(object sender, RoutedEventArgs e)
         {
-            App.OpenDashboard();
+            var user = _appHost.Resolve<IUserManager>().Users.FirstOrDefault(u => u.Configuration.IsAdministrator);
+            App.OpenDashboard(user);
         }
 
         /// <summary>
@@ -331,7 +325,8 @@ namespace MediaBrowser.ServerApplication
         /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
         private void cmdBrowseLibrary_click(object sender, RoutedEventArgs e)
         {
-            App.OpenDashboardPage("index.html");
+            var user = _appHost.Resolve<IUserManager>().Users.FirstOrDefault(u => u.Configuration.IsAdministrator);
+            App.OpenDashboardPage("index.html", user);
         }
 
         /// <summary>

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

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Kernel;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Logging;
 using System.ComponentModel.Composition;
 using System.Threading.Tasks;
@@ -33,16 +34,24 @@ namespace MediaBrowser.WebDashboard.Api
         /// <value>The task manager.</value>
         private readonly ITaskManager _taskManager;
 
+        /// <summary>
+        /// The _user manager
+        /// </summary>
+        private readonly IUserManager _userManager;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="DashboardInfoWebSocketListener" /> class.
         /// </summary>
         /// <param name="kernel">The kernel.</param>
         /// <param name="logger">The logger.</param>
-        public DashboardInfoWebSocketListener(Kernel kernel, ILogger logger, ITaskManager taskManager)
+        /// <param name="taskManager">The task manager.</param>
+        /// <param name="userManager">The user manager.</param>
+        public DashboardInfoWebSocketListener(Kernel kernel, ILogger logger, ITaskManager taskManager, IUserManager userManager)
             : base(logger)
         {
             _kernel = kernel;
             _taskManager = taskManager;
+            _userManager = userManager;
         }
 
         /// <summary>
@@ -52,7 +61,7 @@ namespace MediaBrowser.WebDashboard.Api
         /// <returns>Task{IEnumerable{TaskInfo}}.</returns>
         protected override Task<DashboardInfo> GetDataToSend(object state)
         {
-            return Task.FromResult(DashboardService.GetDashboardInfo(_kernel, Logger, _taskManager));
+            return Task.FromResult(DashboardService.GetDashboardInfo(_kernel, Logger, _taskManager, _userManager));
         }
     }
 }

+ 15 - 5
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -84,13 +84,20 @@ namespace MediaBrowser.WebDashboard.Api
         /// <value>The task manager.</value>
         private readonly ITaskManager _taskManager;
 
+        /// <summary>
+        /// The _user manager
+        /// </summary>
+        private readonly IUserManager _userManager;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="DashboardService" /> class.
         /// </summary>
         /// <param name="taskManager">The task manager.</param>
-        public DashboardService(ITaskManager taskManager)
+        /// <param name="userManager">The user manager.</param>
+        public DashboardService(ITaskManager taskManager, IUserManager userManager)
         {
             _taskManager = taskManager;
+            _userManager = userManager;
         }
 
         /// <summary>
@@ -102,7 +109,7 @@ namespace MediaBrowser.WebDashboard.Api
         {
             var kernel = (Kernel)Kernel;
 
-            return GetDashboardInfo(kernel, Logger, _taskManager);
+            return GetDashboardInfo(kernel, Logger, _taskManager, _userManager);
         }
 
         /// <summary>
@@ -111,10 +118,11 @@ namespace MediaBrowser.WebDashboard.Api
         /// <param name="kernel">The kernel.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="taskManager">The task manager.</param>
+        /// <param name="userManager">The user manager.</param>
         /// <returns>DashboardInfo.</returns>
-        public static DashboardInfo GetDashboardInfo(Kernel kernel, ILogger logger, ITaskManager taskManager)
+        public static DashboardInfo GetDashboardInfo(Kernel kernel, ILogger logger, ITaskManager taskManager, IUserManager userManager)
         {
-            var connections = kernel.UserManager.ActiveConnections.ToArray();
+            var connections = userManager.ConnectedUsers.ToArray();
 
             var dtoBuilder = new DtoBuilder(logger);
 
@@ -130,7 +138,7 @@ namespace MediaBrowser.WebDashboard.Api
 
                 ActiveConnections = connections,
 
-                Users = kernel.Users.Where(u => connections.Any(c => c.UserId == u.Id)).Select(dtoBuilder.GetDtoUser).ToArray()
+                Users = userManager.Users.Where(u => connections.Any(c => c.UserId == u.Id)).Select(dtoBuilder.GetDtoUser).ToArray()
             };
         }
 
@@ -353,6 +361,7 @@ namespace MediaBrowser.WebDashboard.Api
         /// <summary>
         /// Gets the common CSS.
         /// </summary>
+        /// <param name="version">The version.</param>
         /// <returns>System.String.</returns>
         private static string GetCommonCss(Version version)
         {
@@ -373,6 +382,7 @@ namespace MediaBrowser.WebDashboard.Api
         /// <summary>
         /// Gets the common javascript.
         /// </summary>
+        /// <param name="version">The version.</param>
         /// <returns>System.String.</returns>
         private static string GetCommonJavascript(Version version)
         {