Selaa lähdekoodia

removed excess hashing in providers and made user data key-based

Luke Pulverenti 12 vuotta sitten
vanhempi
sitoutus
785deff188
54 muutettua tiedostoa jossa 512 lisäystä ja 678 poistoa
  1. 1 0
      MediaBrowser.Api/Images/ImageService.cs
  2. 10 8
      MediaBrowser.Api/Library/LibraryService.cs
  3. 1 0
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  4. 22 14
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  5. 4 64
      MediaBrowser.Api/UserLibrary/GenresService.cs
  6. 19 35
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  7. 3 62
      MediaBrowser.Api/UserLibrary/PersonsService.cs
  8. 3 62
      MediaBrowser.Api/UserLibrary/StudiosService.cs
  9. 27 16
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  10. 4 3
      MediaBrowser.Api/UserLibrary/YearsService.cs
  11. 8 3
      MediaBrowser.Api/UserService.cs
  12. 0 22
      MediaBrowser.Common/Extensions/BaseExtensions.cs
  13. 1 1
      MediaBrowser.Common/Plugins/BasePlugin.cs
  14. 16 56
      MediaBrowser.Controller/Dto/DtoBuilder.cs
  15. 71 0
      MediaBrowser.Controller/Dto/UserDtoBuilder.cs
  16. 16 27
      MediaBrowser.Controller/Entities/BaseItem.cs
  17. 2 1
      MediaBrowser.Controller/Entities/Folder.cs
  18. 8 0
      MediaBrowser.Controller/Entities/Genre.cs
  19. 5 16
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  20. 8 0
      MediaBrowser.Controller/Entities/Person.cs
  21. 8 0
      MediaBrowser.Controller/Entities/Studio.cs
  22. 9 17
      MediaBrowser.Controller/Entities/TV/Episode.cs
  23. 8 17
      MediaBrowser.Controller/Entities/TV/Season.cs
  24. 4 13
      MediaBrowser.Controller/Entities/TV/Series.cs
  25. 8 0
      MediaBrowser.Controller/Entities/Year.cs
  26. 0 19
      MediaBrowser.Controller/Library/IUserManager.cs
  27. 2 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  28. 6 6
      MediaBrowser.Controller/Persistence/IUserDataRepository.cs
  29. 22 22
      MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
  30. 1 11
      MediaBrowser.Controller/Providers/BaseProviderInfo.cs
  31. 8 1
      MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs
  32. 13 5
      MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs
  33. 25 3
      MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs
  34. 9 1
      MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs
  35. 7 1
      MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs
  36. 9 1
      MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs
  37. 7 0
      MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs
  38. 0 28
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  39. 1 1
      MediaBrowser.Model/Connectivity/ClientConnectionInfo.cs
  40. 1 1
      MediaBrowser.Model/DTO/UserDto.cs
  41. 1 1
      MediaBrowser.Model/Plugins/PluginInfo.cs
  42. 1 1
      MediaBrowser.Model/Querying/ItemQuery.cs
  43. 1 2
      MediaBrowser.Model/Querying/ItemsByNameQuery.cs
  44. 1 1
      MediaBrowser.Model/System/SystemInfo.cs
  45. 6 1
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  46. 13 68
      MediaBrowser.Server.Implementations/Library/UserManager.cs
  47. 11 17
      MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
  48. 8 1
      MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs
  49. 8 1
      MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs
  50. 1 1
      MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs
  51. 62 24
      MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs
  52. 17 19
      MediaBrowser.ServerApplication/ApplicationHost.cs
  53. 2 1
      MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs
  54. 3 2
      MediaBrowser.WebDashboard/Api/DashboardService.cs

+ 1 - 0
MediaBrowser.Api/Images/ImageService.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;

+ 10 - 8
MediaBrowser.Api/Library/LibraryService.cs

@@ -1,6 +1,8 @@
 using MediaBrowser.Common;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using ServiceStack.ServiceHost;
@@ -104,16 +106,16 @@ namespace MediaBrowser.Api.Library
         /// </summary>
         private readonly IApplicationHost _appHost;
         private readonly ILibraryManager _libraryManager;
-        private readonly IUserManager _userManager;
+        private readonly IUserDataRepository _userDataRepository;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryService" /> class.
         /// </summary>
         /// <param name="appHost">The app host.</param>
         /// <param name="libraryManager">The library manager.</param>
-        /// <param name="userManager">The user manager.</param>
+        /// <param name="userDataRepository">The user data repository.</param>
         /// <exception cref="System.ArgumentNullException">appHost</exception>
-        public LibraryService(IApplicationHost appHost, ILibraryManager libraryManager, IUserManager userManager)
+        public LibraryService(IApplicationHost appHost, ILibraryManager libraryManager, IUserDataRepository userDataRepository)
         {
             if (appHost == null)
             {
@@ -122,7 +124,7 @@ namespace MediaBrowser.Api.Library
 
             _appHost = appHost;
             _libraryManager = libraryManager;
-            _userManager = userManager;
+            _userDataRepository = userDataRepository;
         }
 
         /// <summary>
@@ -137,7 +139,7 @@ namespace MediaBrowser.Api.Library
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
 
-            var result = new DtoBuilder(Logger, _libraryManager, _userManager).GetBaseItemDto(item, fields.ToList()).Result;
+            var result = new DtoBuilder(Logger, _libraryManager, _userDataRepository).GetBaseItemDto(item, fields.ToList()).Result;
 
             return ToOptimizedResult(result);
         }
@@ -154,7 +156,7 @@ namespace MediaBrowser.Api.Library
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
 
-            var result = new DtoBuilder(Logger, _libraryManager, _userManager).GetBaseItemDto(item, fields.ToList()).Result;
+            var result = new DtoBuilder(Logger, _libraryManager, _userDataRepository).GetBaseItemDto(item, fields.ToList()).Result;
 
             return ToOptimizedResult(result);
         }
@@ -171,7 +173,7 @@ namespace MediaBrowser.Api.Library
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
 
-            var result = new DtoBuilder(Logger, _libraryManager, _userManager).GetBaseItemDto(item, fields.ToList()).Result;
+            var result = new DtoBuilder(Logger, _libraryManager, _userDataRepository).GetBaseItemDto(item, fields.ToList()).Result;
 
             return ToOptimizedResult(result);
         }
@@ -188,7 +190,7 @@ namespace MediaBrowser.Api.Library
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
 
-            var result = new DtoBuilder(Logger, _libraryManager, _userManager).GetBaseItemDto(item, fields.ToList()).Result;
+            var result = new DtoBuilder(Logger, _libraryManager, _userDataRepository).GetBaseItemDto(item, fields.ToList()).Result;
 
             return ToOptimizedResult(result);
         }

+ 1 - 0
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers.MediaInfo;

+ 22 - 14
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -1,6 +1,7 @@
-using System.Threading;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using ServiceStack.ServiceHost;
@@ -8,6 +9,7 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.UserLibrary
@@ -27,16 +29,19 @@ namespace MediaBrowser.Api.UserLibrary
         /// The library manager
         /// </summary>
         protected readonly ILibraryManager LibraryManager;
+        protected readonly IUserDataRepository UserDataRepository;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseItemsByNameService{TItemType}" /> class.
         /// </summary>
         /// <param name="userManager">The user manager.</param>
         /// <param name="libraryManager">The library manager.</param>
-        protected BaseItemsByNameService(IUserManager userManager, ILibraryManager libraryManager)
+        /// <param name="userDataRepository">The user data repository.</param>
+        protected BaseItemsByNameService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository)
         {
             UserManager = userManager;
             LibraryManager = libraryManager;
+            UserDataRepository = userDataRepository;
         }
 
         /// <summary>
@@ -132,18 +137,19 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>IEnumerable{BaseItem}.</returns>
         private IEnumerable<BaseItem> FilterItems(GetItemsByName request, IEnumerable<BaseItem> items, User user)
         {
-            items = items.AsParallel();
-
-            items = ItemsService.ApplyAdditionalFilters(request, items);
-
-            // Apply filters
-            // Run them starting with the ones that are likely to reduce the list the most
-            foreach (var filter in request.GetFilters().OrderByDescending(f => (int)f))
+            // Exclude item types
+            if (!string.IsNullOrEmpty(request.ExcludeItemTypes))
             {
-                items = ItemsService.ApplyFilter(items, filter, user, UserManager);
+                var vals = request.ExcludeItemTypes.Split(',');
+                items = items.Where(f => !vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
             }
 
-            items = items.AsEnumerable();
+            // Include item types
+            if (!string.IsNullOrEmpty(request.IncludeItemTypes))
+            {
+                var vals = request.IncludeItemTypes.Split(',');
+                items = items.Where(f => vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
+            }
 
             return items;
         }
@@ -185,7 +191,7 @@ namespace MediaBrowser.Api.UserLibrary
                 return null;
             }
 
-            var dto = await new DtoBuilder(Logger, LibraryManager, UserManager).GetBaseItemDto(item, user, fields).ConfigureAwait(false);
+            var dto = await new DtoBuilder(Logger, LibraryManager, UserDataRepository).GetBaseItemDto(item, user, fields).ConfigureAwait(false);
 
             if (fields.Contains(ItemFields.ItemCounts))
             {
@@ -211,13 +217,15 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = await getItem().ConfigureAwait(false);
 
+            var key = item.GetUserDataKey();
+
             // Get the user data for this item
-            var data = await UserManager.GetUserData(user.Id, item.UserDataId).ConfigureAwait(false);
+            var data = await UserDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
 
             // Set favorite status
             data.IsFavorite = isFavorite;
 
-            await UserManager.SaveUserData(user.Id, item.UserDataId, data, CancellationToken.None).ConfigureAwait(false);
+            await UserDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
         }
     }
 

+ 4 - 64
MediaBrowser.Api/UserLibrary/GenresService.cs

@@ -1,6 +1,6 @@
-using System.Threading;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using ServiceStack.ServiceHost;
 using System;
 using System.Collections.Generic;
@@ -18,52 +18,14 @@ namespace MediaBrowser.Api.UserLibrary
     public class GetGenres : GetItemsByName
     {
     }
-
-    [Route("/Users/{UserId}/FavoriteGenres/{Name}", "POST")]
-    [Api(Description = "Marks a genre as a favorite")]
-    public class MarkFavoriteGenre : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid UserId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public string Name { get; set; }
-    }
-
-    [Route("/Users/{UserId}/FavoriteGenres/{Name}", "DELETE")]
-    [Api(Description = "Unmarks a genre as a favorite")]
-    public class UnmarkFavoriteGenre : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public Guid UserId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public string Name { get; set; }
-    }
     
     /// <summary>
     /// Class GenresService
     /// </summary>
     public class GenresService : BaseItemsByNameService<Genre>
     {
-        public GenresService(IUserManager userManager, ILibraryManager libraryManager)
-            : base(userManager, libraryManager)
+        public GenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository)
+            : base(userManager, libraryManager, userDataRepository)
         {
         }
 
@@ -79,28 +41,6 @@ namespace MediaBrowser.Api.UserLibrary
             return ToOptimizedResult(result);
         }
 
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public void Post(MarkFavoriteGenre request)
-        {
-            var task = MarkFavorite(() => LibraryManager.GetGenre(request.Name), request.UserId, true);
-
-            Task.WaitAll(task);
-        }
-
-        /// <summary>
-        /// Deletes the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public void Delete(UnmarkFavoriteGenre request)
-        {
-            var task = MarkFavorite(() => LibraryManager.GetGenre(request.Name), request.UserId, false);
-
-            Task.WaitAll(task);
-        }
-
         /// <summary>
         /// Gets all items.
         /// </summary>

+ 19 - 35
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -1,6 +1,8 @@
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using ServiceStack.ServiceHost;
@@ -148,6 +150,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// The _user manager
         /// </summary>
         private readonly IUserManager _userManager;
+        private readonly IUserDataRepository _userDataRepository;
 
         /// <summary>
         /// The _library manager
@@ -161,11 +164,13 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="userManager">The user manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="searchEngine">The search engine.</param>
-        public ItemsService(IUserManager userManager, ILibraryManager libraryManager, ILibrarySearchEngine searchEngine)
+        /// <param name="userDataRepository">The user data repository.</param>
+        public ItemsService(IUserManager userManager, ILibraryManager libraryManager, ILibrarySearchEngine searchEngine, IUserDataRepository userDataRepository)
         {
             _userManager = userManager;
             _libraryManager = libraryManager;
             _searchEngine = searchEngine;
+            _userDataRepository = userDataRepository;
         }
 
         /// <summary>
@@ -199,7 +204,7 @@ namespace MediaBrowser.Api.UserLibrary
             // Run them starting with the ones that are likely to reduce the list the most
             foreach (var filter in request.GetFilters().OrderByDescending(f => (int)f))
             {
-                items = ApplyFilter(items, filter, user, _userManager);
+                items = ApplyFilter(items, filter, user, _userDataRepository);
             }
 
             items = items.AsEnumerable();
@@ -214,7 +219,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             var fields = request.GetItemFields().ToList();
 
-            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userManager);
+            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository);
 
             var returnItems = await Task.WhenAll(pagedItems.Select(i => dtoBuilder.GetBaseItemDto(i, user, fields))).ConfigureAwait(false);
 
@@ -274,9 +279,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="items">The items.</param>
         /// <param name="filter">The filter.</param>
         /// <param name="user">The user.</param>
-        /// <param name="userManager">The user manager.</param>
+        /// <param name="repository">The repository.</param>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        internal static IEnumerable<BaseItem> ApplyFilter(IEnumerable<BaseItem> items, ItemFilter filter, User user, IUserManager userManager)
+        internal static IEnumerable<BaseItem> ApplyFilter(IEnumerable<BaseItem> items, ItemFilter filter, User user, IUserDataRepository repository)
         {
             // Avoids implicitly captured closure
             var currentUser = user;
@@ -286,7 +291,7 @@ namespace MediaBrowser.Api.UserLibrary
                 case ItemFilter.Likes:
                     return items.Where(item =>
                     {
-                        var userdata = userManager.GetUserData(user.Id, item.UserDataId).Result;
+                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result;
 
                         return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
                     });
@@ -294,7 +299,7 @@ namespace MediaBrowser.Api.UserLibrary
                 case ItemFilter.Dislikes:
                     return items.Where(item =>
                     {
-                        var userdata = userManager.GetUserData(user.Id, item.UserDataId).Result;
+                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result;
 
                         return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
                     });
@@ -302,7 +307,7 @@ namespace MediaBrowser.Api.UserLibrary
                 case ItemFilter.IsFavorite:
                     return items.Where(item =>
                     {
-                        var userdata = userManager.GetUserData(user.Id, item.UserDataId).Result;
+                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result;
 
                         return userdata != null && userdata.IsFavorite;
                     });
@@ -313,7 +318,7 @@ namespace MediaBrowser.Api.UserLibrary
                 case ItemFilter.IsResumable:
                     return items.Where(item =>
                     {
-                        var userdata = userManager.GetUserData(user.Id, item.UserDataId).Result;
+                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result;
 
                         return userdata != null && userdata.PlaybackPositionTicks > 0;
                     });
@@ -321,7 +326,7 @@ namespace MediaBrowser.Api.UserLibrary
                 case ItemFilter.IsPlayed:
                     return items.Where(item =>
                     {
-                        var userdata = userManager.GetUserData(user.Id, item.UserDataId).Result;
+                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result;
 
                         return userdata != null && userdata.PlayCount > 0;
                     });
@@ -329,7 +334,7 @@ namespace MediaBrowser.Api.UserLibrary
                 case ItemFilter.IsUnplayed:
                     return items.Where(item =>
                     {
-                        var userdata = userManager.GetUserData(user.Id, item.UserDataId).Result;
+                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()).Result;
 
                         return userdata == null || userdata.PlayCount == 0;
                     });
@@ -347,32 +352,11 @@ namespace MediaBrowser.Api.UserLibrary
         /// <summary>
         /// Applies the additional filters.
         /// </summary>
-        /// <param name="itemsRequest">The items request.</param>
+        /// <param name="request">The request.</param>
         /// <param name="items">The items.</param>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        internal static IEnumerable<BaseItem> ApplyAdditionalFilters(BaseItemsRequest itemsRequest, IEnumerable<BaseItem> items)
+        internal static IEnumerable<BaseItem> ApplyAdditionalFilters(GetItems request, IEnumerable<BaseItem> items)
         {
-            // Exclude item types
-            if (!string.IsNullOrEmpty(itemsRequest.ExcludeItemTypes))
-            {
-                var vals = itemsRequest.ExcludeItemTypes.Split(',');
-                items = items.Where(f => !vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
-            }
-
-            // Include item types
-            if (!string.IsNullOrEmpty(itemsRequest.IncludeItemTypes))
-            {
-                var vals = itemsRequest.IncludeItemTypes.Split(',');
-                items = items.Where(f => vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
-            }
-            
-            var request = itemsRequest as GetItems;
-
-            if (request == null)
-            {
-                return items;
-            }
-
             // Filter by Series Status
             if (!string.IsNullOrEmpty(request.SeriesStatus))
             {

+ 3 - 62
MediaBrowser.Api/UserLibrary/PersonsService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using ServiceStack.ServiceHost;
 using System;
 using System.Collections.Generic;
@@ -22,52 +23,14 @@ namespace MediaBrowser.Api.UserLibrary
         /// <value>The person types.</value>
         public string PersonTypes { get; set; }
     }
-
-    [Route("/Users/{UserId}/FavoritePersons/{Name}", "POST")]
-    [Api(Description = "Marks a person as a favorite")]
-    public class MarkFavoritePerson : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid UserId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public string Name { get; set; }
-    }
-
-    [Route("/Users/{UserId}/FavoritePersons/{Name}", "DELETE")]
-    [Api(Description = "Unmarks a person as a favorite")]
-    public class UnmarkFavoritePerson : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public Guid UserId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public string Name { get; set; }
-    }
     
     /// <summary>
     /// Class PersonsService
     /// </summary>
     public class PersonsService : BaseItemsByNameService<Person>
     {
-        public PersonsService(IUserManager userManager, ILibraryManager libraryManager)
-            : base(userManager, libraryManager)
+        public PersonsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository)
+            : base(userManager, libraryManager, userDataRepository)
         {
         }
 
@@ -83,28 +46,6 @@ namespace MediaBrowser.Api.UserLibrary
             return ToOptimizedResult(result);
         }
 
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public void Post(MarkFavoritePerson request)
-        {
-            var task = MarkFavorite(() => LibraryManager.GetPerson(request.Name), request.UserId, true);
-
-            Task.WaitAll(task);
-        }
-
-        /// <summary>
-        /// Deletes the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public void Delete(UnmarkFavoritePerson request)
-        {
-            var task = MarkFavorite(() => LibraryManager.GetPerson(request.Name), request.UserId, false);
-
-            Task.WaitAll(task);
-        }
-
         /// <summary>
         /// Gets all items.
         /// </summary>

+ 3 - 62
MediaBrowser.Api/UserLibrary/StudiosService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using ServiceStack.ServiceHost;
 using System;
 using System.Collections.Generic;
@@ -17,52 +18,14 @@ namespace MediaBrowser.Api.UserLibrary
     public class GetStudios : GetItemsByName
     {
     }
-
-    [Route("/Users/{UserId}/FavoriteStudios/{Name}", "POST")]
-    [Api(Description = "Marks a studio as a favorite")]
-    public class MarkFavoriteStudio : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid UserId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public string Name { get; set; }
-    }
-
-    [Route("/Users/{UserId}/FavoriteStudios/{Name}", "DELETE")]
-    [Api(Description = "Unmarks a studio as a favorite")]
-    public class UnmarkFavoriteStudio : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public Guid UserId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public string Name { get; set; }
-    }
     
     /// <summary>
     /// Class StudiosService
     /// </summary>
     public class StudiosService : BaseItemsByNameService<Studio>
     {
-        public StudiosService(IUserManager userManager, ILibraryManager libraryManager)
-            : base(userManager, libraryManager)
+        public StudiosService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository)
+            : base(userManager, libraryManager, userDataRepository)
         {
         }
 
@@ -77,28 +40,6 @@ namespace MediaBrowser.Api.UserLibrary
 
             return ToOptimizedResult(result);
         }
-
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public void Post(MarkFavoriteStudio request)
-        {
-            var task = MarkFavorite(() => LibraryManager.GetStudio(request.Name), request.UserId, true);
-
-            Task.WaitAll(task);
-        }
-
-        /// <summary>
-        /// Deletes the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public void Delete(UnmarkFavoriteStudio request)
-        {
-            var task = MarkFavorite(() => LibraryManager.GetStudio(request.Name), request.UserId, false);
-
-            Task.WaitAll(task);
-        }
         
         /// <summary>
         /// Gets all items.

+ 27 - 16
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -1,6 +1,8 @@
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using ServiceStack.ServiceHost;
@@ -335,18 +337,19 @@ namespace MediaBrowser.Api.UserLibrary
         /// The _user manager
         /// </summary>
         private readonly IUserManager _userManager;
-
+        private readonly IUserDataRepository _userDataRepository;
         private readonly ILibraryManager _libraryManager;
         
         /// <summary>
         /// Initializes a new instance of the <see cref="UserLibraryService" /> class.
         /// </summary>
         /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
-        public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager)
+        public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository)
             : base()
         {
             _userManager = userManager;
             _libraryManager = libraryManager;
+            _userDataRepository = userDataRepository;
         }
 
         /// <summary>
@@ -365,7 +368,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             var movie = (Movie)item;
 
-            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userManager);
+            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository);
 
             var items = movie.SpecialFeatures.Select(i => dtoBuilder.GetBaseItemDto(i, user, fields)).AsParallel().Select(t => t.Result).ToList();
 
@@ -386,7 +389,7 @@ namespace MediaBrowser.Api.UserLibrary
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
 
-            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userManager);
+            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository);
 
             var items = item.LocalTrailers.Select(i => dtoBuilder.GetBaseItemDto(i, user, fields)).AsParallel().Select(t => t.Result).ToList();
 
@@ -407,7 +410,7 @@ namespace MediaBrowser.Api.UserLibrary
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
 
-            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userManager);
+            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository);
 
             var result = dtoBuilder.GetBaseItemDto(item, user, fields).Result;
 
@@ -423,7 +426,7 @@ namespace MediaBrowser.Api.UserLibrary
             // Get everything
             var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
 
-            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userManager);
+            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository);
 
             var result = dtoBuilder.GetBaseItemDto(item, user, fields).Result;
 
@@ -457,12 +460,14 @@ namespace MediaBrowser.Api.UserLibrary
             var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
 
             // Get the user data for this item
-            var data = _userManager.GetUserData(user.Id, item.UserDataId).Result;
+            var key = item.GetUserDataKey();
+
+            var data = _userDataRepository.GetUserData(user.Id, key).Result;
 
             // Set favorite status
             data.IsFavorite = true;
 
-            var task = _userManager.SaveUserData(user.Id, item.UserDataId, data, CancellationToken.None);
+            var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None);
 
             Task.WaitAll(task);
         }
@@ -477,13 +482,15 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
 
+            var key = item.GetUserDataKey();
+            
             // Get the user data for this item
-            var data = _userManager.GetUserData(user.Id, item.UserDataId).Result;
+            var data = _userDataRepository.GetUserData(user.Id, key).Result;
 
             // Set favorite status
             data.IsFavorite = false;
 
-            var task = _userManager.SaveUserData(user.Id, item.UserDataId, data, CancellationToken.None);
+            var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None);
 
             Task.WaitAll(task);
         }
@@ -498,12 +505,14 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
 
+            var key = item.GetUserDataKey();
+            
             // Get the user data for this item
-            var data = _userManager.GetUserData(user.Id, item.UserDataId).Result;
+            var data = _userDataRepository.GetUserData(user.Id, key).Result;
 
             data.Rating = null;
 
-            var task = _userManager.SaveUserData(user.Id, item.UserDataId, data, CancellationToken.None);
+            var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None);
 
             Task.WaitAll(task);
         }
@@ -518,12 +527,14 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
 
+            var key = item.GetUserDataKey();
+            
             // Get the user data for this item
-            var data = _userManager.GetUserData(user.Id, item.UserDataId).Result;
+            var data = _userDataRepository.GetUserData(user.Id, key).Result;
 
             data.Likes = request.Likes;
 
-            var task = _userManager.SaveUserData(user.Id, item.UserDataId, data, CancellationToken.None);
+            var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None);
 
             Task.WaitAll(task);
         }
@@ -623,7 +634,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var item = DtoBuilder.GetItemByClientId(itemId, _userManager, _libraryManager, user.Id);
 
-            return item.SetPlayedStatus(user, wasPlayed, _userManager);
+            return item.SetPlayedStatus(user, wasPlayed, _userDataRepository);
         }
     }
 }

+ 4 - 3
MediaBrowser.Api/UserLibrary/YearsService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using ServiceStack.ServiceHost;
 using System;
 using System.Collections.Generic;
@@ -28,9 +29,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// The us culture
         /// </summary>
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-        
-        public YearsService(IUserManager userManager, ILibraryManager libraryManager)
-            : base(userManager, libraryManager)
+
+        public YearsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository)
+            : base(userManager, libraryManager, userDataRepository)
         {
         }
 

+ 8 - 3
MediaBrowser.Api/UserService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Serialization;
@@ -165,7 +166,7 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         public object Get(GetUsers request)
         {
-            var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userManager);
+            var dtoBuilder = new UserDtoBuilder(Logger);
 
             var users = _userManager.Users.OrderBy(u => u.Name).Select(dtoBuilder.GetUserDto).ToArray();
 
@@ -186,7 +187,9 @@ namespace MediaBrowser.Api
                 throw new ResourceNotFoundException("User not found");
             }
 
-            var result = new DtoBuilder(Logger, _libraryManager, _userManager).GetUserDto(user);
+            var dtoBuilder = new UserDtoBuilder(Logger);
+
+            var result = dtoBuilder.GetUserDto(user);
 
             return ToOptimizedResult(result);
         }
@@ -300,7 +303,9 @@ namespace MediaBrowser.Api
 
             newUser.UpdateConfiguration(dtoUser.Configuration, _xmlSerializer);
 
-            var result = new DtoBuilder(Logger, _libraryManager, _userManager).GetUserDto(newUser);
+            var dtoBuilder = new UserDtoBuilder(Logger);
+
+            var result = dtoBuilder.GetUserDto(newUser);
 
             return ToOptimizedResult(result);
         }

+ 0 - 22
MediaBrowser.Common/Extensions/BaseExtensions.cs

@@ -1,6 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Linq;
 using System.Security.Cryptography;
 using System.Text;
 using System.Text.RegularExpressions;
@@ -55,26 +53,6 @@ namespace MediaBrowser.Common.Extensions
             return (aType.FullName + str.ToLower()).GetMD5();
         }
 
-        /// <summary>
-        /// Helper method for Dictionaries since they throw on not-found keys
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <typeparam name="U"></typeparam>
-        /// <param name="dictionary">The dictionary.</param>
-        /// <param name="key">The key.</param>
-        /// <param name="defaultValue">The default value.</param>
-        /// <returns>``1.</returns>
-        public static U GetValueOrDefault<T, U>(this Dictionary<T, U> dictionary, T key, U defaultValue)
-        {
-            U val;
-            if (!dictionary.TryGetValue(key, out val))
-            {
-                val = defaultValue;
-            }
-            return val;
-
-        }
-
         /// <summary>
         /// Gets the attribute value.
         /// </summary>

+ 1 - 1
MediaBrowser.Common/Plugins/BasePlugin.cs

@@ -302,7 +302,7 @@ namespace MediaBrowser.Common.Plugins
                 AssemblyFileName = AssemblyFileName,
                 ConfigurationDateLastModified = ConfigurationDateLastModified,
                 Description = Description,
-                Id = Id,
+                Id = Id.ToString(),
                 EnableAutoUpdate = Configuration.EnableAutoUpdate,
                 UpdateClass = Configuration.UpdateClass,
                 ConfigurationFileName = ConfigurationFileName

+ 16 - 56
MediaBrowser.Controller/Library/DtoBuilder.cs → MediaBrowser.Controller/Dto/DtoBuilder.cs

@@ -3,6 +3,8 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
@@ -14,7 +16,7 @@ using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 
-namespace MediaBrowser.Controller.Library
+namespace MediaBrowser.Controller.Dto
 {
     /// <summary>
     /// Generates DTO's from domain entities
@@ -28,13 +30,13 @@ namespace MediaBrowser.Controller.Library
 
         private readonly ILogger _logger;
         private readonly ILibraryManager _libraryManager;
-        private readonly IUserManager _userManager;
+        private readonly IUserDataRepository _userDataRepository;
 
-        public DtoBuilder(ILogger logger, ILibraryManager libraryManager, IUserManager userManager)
+        public DtoBuilder(ILogger logger, ILibraryManager libraryManager, IUserDataRepository userDataRepository)
         {
             _logger = logger;
             _libraryManager = libraryManager;
-            _userManager = userManager;
+            _userDataRepository = userDataRepository;
         }
 
         /// <summary>
@@ -73,7 +75,7 @@ namespace MediaBrowser.Controller.Library
             {
                 try
                 {
-                    AttachPrimaryImageAspectRatio(dto, item);
+                    AttachPrimaryImageAspectRatio(dto, item, _logger);
                 }
                 catch (Exception ex)
                 {
@@ -136,7 +138,7 @@ namespace MediaBrowser.Controller.Library
             {
                 try
                 {
-                    AttachPrimaryImageAspectRatio(dto, item);
+                    AttachPrimaryImageAspectRatio(dto, item, _logger);
                 }
                 catch (Exception ex)
                 {
@@ -167,7 +169,7 @@ namespace MediaBrowser.Controller.Library
         {
             if (fields.Contains(ItemFields.UserData))
             {
-                var userData = await _userManager.GetUserData(user.Id, item.UserDataId).ConfigureAwait(false);
+                var userData = await _userDataRepository.GetUserData(user.Id, item.GetUserDataKey()).ConfigureAwait(false);
 
                 dto.UserData = GetUserItemDataDto(userData);
             }
@@ -186,18 +188,19 @@ namespace MediaBrowser.Controller.Library
                     // Skip sorting since all we want is a count
                     dto.ChildCount = folder.GetChildren(user).Count();
 
-                    await SetSpecialCounts(folder, user, dto, _userManager).ConfigureAwait(false);
+                    await SetSpecialCounts(folder, user, dto, _userDataRepository).ConfigureAwait(false);
                 }
             }
         }
-        
+
         /// <summary>
         /// Attaches the primary image aspect ratio.
         /// </summary>
         /// <param name="dto">The dto.</param>
         /// <param name="item">The item.</param>
+        /// <param name="_logger">The _logger.</param>
         /// <returns>Task.</returns>
-        private void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
+        internal static void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item, ILogger _logger)
         {
             var path = item.PrimaryImagePath;
 
@@ -503,9 +506,9 @@ namespace MediaBrowser.Controller.Library
         /// <param name="folder">The folder.</param>
         /// <param name="user">The user.</param>
         /// <param name="dto">The dto.</param>
-        /// <param name="userManager">The user manager.</param>
+        /// <param name="userDataRepository">The user data repository.</param>
         /// <returns>Task.</returns>
-        private static async Task SetSpecialCounts(Folder folder, User user, BaseItemDto dto, IUserManager userManager)
+        private static async Task SetSpecialCounts(Folder folder, User user, BaseItemDto dto, IUserDataRepository userDataRepository)
         {
             var rcentlyAddedItemCount = 0;
             var recursiveItemCount = 0;
@@ -515,7 +518,7 @@ namespace MediaBrowser.Controller.Library
             // Loop through each recursive child
             foreach (var child in folder.GetRecursiveChildren(user).Where(i => !i.IsFolder))
             {
-                var userdata = await userManager.GetUserData(user.Id, child.UserDataId).ConfigureAwait(false);
+                var userdata = await userDataRepository.GetUserData(user.Id, child.GetUserDataKey()).ConfigureAwait(false);
 
                 recursiveItemCount++;
 
@@ -785,49 +788,6 @@ namespace MediaBrowser.Controller.Library
             return item.Id.ToString();
         }
 
-        /// <summary>
-        /// Converts a User to a DTOUser
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <returns>DtoUser.</returns>
-        /// <exception cref="System.ArgumentNullException">user</exception>
-        public UserDto GetUserDto(User user)
-        {
-            if (user == null)
-            {
-                throw new ArgumentNullException("user");
-            }
-
-            var dto = new UserDto
-            {
-                Id = user.Id,
-                Name = user.Name,
-                HasPassword = !String.IsNullOrEmpty(user.Password),
-                LastActivityDate = user.LastActivityDate,
-                LastLoginDate = user.LastLoginDate,
-                Configuration = user.Configuration
-            };
-            
-            var image = user.PrimaryImagePath;
-
-            if (!string.IsNullOrEmpty(image))
-            {
-                dto.PrimaryImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(user, ImageType.Primary, image);
-
-                try
-                {
-                    AttachPrimaryImageAspectRatio(dto, user);
-                }
-                catch (Exception ex)
-                {
-                    // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
-                    _logger.ErrorException("Error generating PrimaryImageAspectRatio for {0}", ex, user.Name);
-                }
-            }
-            
-            return dto;
-        }
-
         /// <summary>
         /// Gets a BaseItem based upon it's client-side item id
         /// </summary>

+ 71 - 0
MediaBrowser.Controller/Dto/UserDtoBuilder.cs

@@ -0,0 +1,71 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System;
+
+namespace MediaBrowser.Controller.Dto
+{
+    /// <summary>
+    /// Class UserDtoBuilder
+    /// </summary>
+    public class UserDtoBuilder
+    {
+        /// <summary>
+        /// The _logger
+        /// </summary>
+        private readonly ILogger _logger;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="UserDtoBuilder"/> class.
+        /// </summary>
+        /// <param name="logger">The logger.</param>
+        public UserDtoBuilder(ILogger logger)
+        {
+            _logger = logger;
+        }
+
+        /// <summary>
+        /// Converts a User to a DTOUser
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <returns>DtoUser.</returns>
+        /// <exception cref="System.ArgumentNullException">user</exception>
+        public UserDto GetUserDto(User user)
+        {
+            if (user == null)
+            {
+                throw new ArgumentNullException("user");
+            }
+
+            var dto = new UserDto
+            {
+                Id = user.Id.ToString(),
+                Name = user.Name,
+                HasPassword = !String.IsNullOrEmpty(user.Password),
+                LastActivityDate = user.LastActivityDate,
+                LastLoginDate = user.LastLoginDate,
+                Configuration = user.Configuration
+            };
+
+            var image = user.PrimaryImagePath;
+
+            if (!string.IsNullOrEmpty(image))
+            {
+                dto.PrimaryImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(user, ImageType.Primary, image);
+
+                try
+                {
+                    DtoBuilder.AttachPrimaryImageAspectRatio(dto, user, _logger);
+                }
+                catch (Exception ex)
+                {
+                    // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
+                    _logger.ErrorException("Error generating PrimaryImageAspectRatio for {0}", ex, user.Name);
+                }
+            }
+
+            return dto;
+        }
+    }
+}

+ 16 - 27
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Model.Entities;
@@ -183,23 +184,18 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// The _file system stamp
         /// </summary>
-        private Guid? _fileSystemStamp;
+        private string _fileSystemStamp;
         /// <summary>
         /// Gets a directory stamp, in the form of a string, that can be used for
         /// comparison purposes to determine if the file system entries for this item have changed.
         /// </summary>
         /// <value>The file system stamp.</value>
         [IgnoreDataMember]
-        public Guid FileSystemStamp
+        public string FileSystemStamp
         {
             get
             {
-                if (!_fileSystemStamp.HasValue)
-                {
-                    _fileSystemStamp = GetFileSystemStamp();
-                }
-
-                return _fileSystemStamp.Value;
+                return _fileSystemStamp ?? (_fileSystemStamp = GetFileSystemStamp());
             }
         }
 
@@ -221,12 +217,12 @@ namespace MediaBrowser.Controller.Entities
         /// comparison purposes to determine if the file system entries for this item have changed.
         /// </summary>
         /// <returns>Guid.</returns>
-        private Guid GetFileSystemStamp()
+        private string GetFileSystemStamp()
         {
             // If there's no path or the item is a file, there's nothing to do
             if (LocationType != LocationType.FileSystem || !ResolveArgs.IsDirectory)
             {
-                return Guid.Empty;
+                return string.Empty;
             }
 
             var sb = new StringBuilder();
@@ -242,7 +238,7 @@ namespace MediaBrowser.Controller.Entities
                 sb.Append(file.cFileName);
             }
 
-            return sb.ToString().GetMD5();
+            return sb.ToString();
         }
 
         /// <summary>
@@ -820,21 +816,12 @@ namespace MediaBrowser.Controller.Entities
         }
 
         /// <summary>
-        /// The _user data id
+        /// Gets the user data key.
         /// </summary>
-        protected Guid _userDataId; //cache this so it doesn't have to be re-constructed on every reference
-        /// <summary>
-        /// Return the id that should be used to key user data for this item.
-        /// Default is just this Id but subclasses can use provider Ids for transportability.
-        /// </summary>
-        /// <value>The user data id.</value>
-        [IgnoreDataMember]
-        public virtual Guid UserDataId
+        /// <returns>System.String.</returns>
+        public virtual string GetUserDataKey()
         {
-            get
-            {
-                return _userDataId == Guid.Empty ? (_userDataId = Id) : _userDataId;
-            }
+            return Id.ToString();
         }
 
         /// <summary>
@@ -1151,14 +1138,16 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="userManager">The user manager.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
-        public virtual async Task SetPlayedStatus(User user, bool wasPlayed, IUserManager userManager)
+        public virtual async Task SetPlayedStatus(User user, bool wasPlayed, IUserDataRepository userManager)
         {
             if (user == null)
             {
                 throw new ArgumentNullException();
             }
 
-            var data = await userManager.GetUserData(user.Id, UserDataId).ConfigureAwait(false);
+            var key = GetUserDataKey();
+
+            var data = await userManager.GetUserData(user.Id, key).ConfigureAwait(false);
 
             if (wasPlayed)
             {
@@ -1181,7 +1170,7 @@ namespace MediaBrowser.Controller.Entities
 
             data.Played = wasPlayed;
 
-            await userManager.SaveUserData(user.Id, UserDataId, data, CancellationToken.None).ConfigureAwait(false);
+            await userManager.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
         }
 
         /// <summary>

+ 2 - 1
MediaBrowser.Controller/Entities/Folder.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Model.Entities;
 using System;
@@ -809,7 +810,7 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="wasPlayed">if set to <c>true</c> [was played].</param>
         /// <param name="userManager">The user manager.</param>
         /// <returns>Task.</returns>
-        public override async Task SetPlayedStatus(User user, bool wasPlayed, IUserManager userManager)
+        public override async Task SetPlayedStatus(User user, bool wasPlayed, IUserDataRepository userManager)
         {
             await base.SetPlayedStatus(user, wasPlayed, userManager).ConfigureAwait(false);
 

+ 8 - 0
MediaBrowser.Controller/Entities/Genre.cs

@@ -6,5 +6,13 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public class Genre : BaseItem
     {
+        /// <summary>
+        /// Gets the user data key.
+        /// </summary>
+        /// <returns>System.String.</returns>
+        public override string GetUserDataKey()
+        {
+            return Name;
+        }
     }
 }

+ 5 - 16
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -1,7 +1,5 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.Entities;
-using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
@@ -30,21 +28,12 @@ namespace MediaBrowser.Controller.Entities.Movies
         }
 
         /// <summary>
-        /// Override to use tmdb or imdb id so it will stick if the item moves physical locations
+        /// Gets the user data key.
         /// </summary>
-        /// <value>The user data id.</value>
-        [IgnoreDataMember]
-        public override Guid UserDataId
+        /// <returns>System.String.</returns>
+        public override string GetUserDataKey()
         {
-            get
-            {
-                if (_userDataId == Guid.Empty)
-                {
-                    var baseId = this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb);
-                    _userDataId = baseId != null ? baseId.GetMD5() : Id;
-                }
-                return _userDataId;
-            }
+            return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
         }
 
         /// <summary>

+ 8 - 0
MediaBrowser.Controller/Entities/Person.cs

@@ -6,6 +6,14 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public class Person : BaseItem
     {
+        /// <summary>
+        /// Gets the user data key.
+        /// </summary>
+        /// <returns>System.String.</returns>
+        public override string GetUserDataKey()
+        {
+            return Name;
+        }
     }
 
     /// <summary>

+ 8 - 0
MediaBrowser.Controller/Entities/Studio.cs

@@ -6,5 +6,13 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public class Studio : BaseItem
     {
+        /// <summary>
+        /// Gets the user data key.
+        /// </summary>
+        /// <returns>System.String.</returns>
+        public override string GetUserDataKey()
+        {
+            return Name;
+        }
     }
 }

+ 9 - 17
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -49,27 +49,19 @@ namespace MediaBrowser.Controller.Entities.TV
         }
 
         /// <summary>
-        /// Override to use the provider Ids + season and episode number so it will be portable
+        /// Gets the user data key.
         /// </summary>
-        /// <value>The user data id.</value>
-        [IgnoreDataMember]
-        public override Guid UserDataId
+        /// <returns>System.String.</returns>
+        public override string GetUserDataKey()
         {
-            get
+            if (Series != null)
             {
-                if (_userDataId == Guid.Empty)
-                {
-                    var baseId = Series != null ? Series.GetProviderId(MetadataProviders.Tvdb) ?? Series.GetProviderId(MetadataProviders.Tvcom) : null;
-                    if (baseId != null)
-                    {
-                        var seasonNo = Season != null ? Season.IndexNumber ?? 0 : 0;
-                        var epNo = IndexNumber ?? 0;
-                        baseId = baseId + seasonNo.ToString("000") + epNo.ToString("000");
-                    }
-                    _userDataId = baseId != null ? baseId.GetMD5() : Id;
-                }
-                return _userDataId;
+                var seasonNo = Season != null ? Season.IndexNumber ?? 0 : 0;
+                var epNo = IndexNumber ?? 0;
+                return Series.GetUserDataKey() + seasonNo.ToString("000") + epNo.ToString("000");
             }
+
+            return base.GetUserDataKey();
         }
 
         /// <summary>

+ 8 - 17
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -67,27 +67,18 @@ namespace MediaBrowser.Controller.Entities.TV
         }
 
         /// <summary>
-        /// Override to use the provider Ids + season number so it will be portable
+        /// Gets the user data key.
         /// </summary>
-        /// <value>The user data id.</value>
-        [IgnoreDataMember]
-        public override Guid UserDataId
+        /// <returns>System.String.</returns>
+        public override string GetUserDataKey()
         {
-            get
+            if (Series != null)
             {
-                if (_userDataId == Guid.Empty)
-                {
-                    var baseId = Series != null ? Series.GetProviderId(MetadataProviders.Tvdb) ?? Series.GetProviderId(MetadataProviders.Tvcom) : null;
-                    if (baseId != null)
-                    {
-                        var seasonNo = IndexNumber ?? 0;
-                        baseId =  baseId + seasonNo.ToString("000");
-                    }
-        
-                    _userDataId = baseId != null ? baseId.GetMD5() : Id;
-                }
-                return _userDataId;
+                var seasonNo = IndexNumber ?? 0;
+                return Series.GetUserDataKey() + seasonNo.ToString("000");
             }
+
+            return base.GetUserDataKey();
         }
 
         /// <summary>

+ 4 - 13
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -44,21 +44,12 @@ namespace MediaBrowser.Controller.Entities.TV
         }
 
         /// <summary>
-        /// Override to use the provider Ids so it will be portable
+        /// Gets the user data key.
         /// </summary>
-        /// <value>The user data id.</value>
-        [IgnoreDataMember]
-        public override Guid UserDataId
+        /// <returns>System.String.</returns>
+        public override string GetUserDataKey()
         {
-            get
-            {
-                if (_userDataId == Guid.Empty)
-                {
-                    var baseId = this.GetProviderId(MetadataProviders.Tvdb) ?? this.GetProviderId(MetadataProviders.Tvcom);
-                    _userDataId = baseId != null ? baseId.GetMD5() : Id;
-                }
-                return _userDataId;
-            }
+            return this.GetProviderId(MetadataProviders.Tvdb) ?? this.GetProviderId(MetadataProviders.Tvcom) ?? base.GetUserDataKey();
         }
 
         // Studio, Genre and Rating will all be the same so makes no sense to index by these

+ 8 - 0
MediaBrowser.Controller/Entities/Year.cs

@@ -6,5 +6,13 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public class Year : BaseItem
     {
+        /// <summary>
+        /// Gets the user data key.
+        /// </summary>
+        /// <returns>System.String.</returns>
+        public override string GetUserDataKey()
+        {
+            return Name;
+        }
     }
 }

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

@@ -173,24 +173,5 @@ namespace MediaBrowser.Controller.Library
         /// <param name="newPassword">The new password.</param>
         /// <returns>Task.</returns>
         Task ChangePassword(User user, string newPassword);
-
-        /// <summary>
-        /// Saves the user data.
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="userDataId">The user data id.</param>
-        /// <param name="userData">The user data.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task SaveUserData(Guid userId, Guid userDataId, UserItemData userData,
-                                    CancellationToken cancellationToken);
-
-        /// <summary>
-        /// Gets the user data.
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="userDataId">The user data id.</param>
-        /// <returns>Task{UserItemData}.</returns>
-        Task<UserItemData> GetUserData(Guid userId, Guid userDataId);
     }
 }

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

@@ -72,6 +72,7 @@
     <Compile Include="Drawing\ImageExtensions.cs" />
     <Compile Include="Drawing\ImageHeader.cs" />
     <Compile Include="Drawing\ImageManager.cs" />
+    <Compile Include="Dto\UserDtoBuilder.cs" />
     <Compile Include="Entities\AggregateFolder.cs" />
     <Compile Include="Entities\Audio\Audio.cs" />
     <Compile Include="Entities\Audio\MusicAlbum.cs" />
@@ -107,7 +108,7 @@
     <Compile Include="IServerApplicationHost.cs" />
     <Compile Include="IServerApplicationPaths.cs" />
     <Compile Include="Library\ChildrenChangedEventArgs.cs" />
-    <Compile Include="Library\DtoBuilder.cs" />
+    <Compile Include="Dto\DtoBuilder.cs" />
     <Compile Include="Providers\IProviderManager.cs" />
     <Compile Include="Providers\MediaInfo\MediaEncoderHelpers.cs" />
     <Compile Include="Providers\MetadataProviderPriority.cs" />

+ 6 - 6
MediaBrowser.Controller/Persistence/IUserDataRepository.cs

@@ -1,5 +1,5 @@
-using MediaBrowser.Controller.Entities;
-using System;
+using System;
+using MediaBrowser.Controller.Entities;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -14,19 +14,19 @@ namespace MediaBrowser.Controller.Persistence
         /// Saves the user data.
         /// </summary>
         /// <param name="userId">The user id.</param>
-        /// <param name="userDataId">The user data id.</param>
+        /// <param name="key">The key.</param>
         /// <param name="userData">The user data.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task SaveUserData(Guid userId, Guid userDataId, UserItemData userData,
+        Task SaveUserData(Guid userId, string key, UserItemData userData,
                                     CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the user data.
         /// </summary>
         /// <param name="userId">The user id.</param>
-        /// <param name="userDataId">The user data id.</param>
+        /// <param name="key">The key.</param>
         /// <returns>Task{UserItemData}.</returns>
-        Task<UserItemData> GetUserData(Guid userId, Guid userDataId);
+        Task<UserItemData> GetUserData(Guid userId, string key);
     }
 }

+ 22 - 22
MediaBrowser.Controller/Providers/BaseMetadataProvider.cs

@@ -29,19 +29,7 @@ namespace MediaBrowser.Controller.Providers
         /// <summary>
         /// The _id
         /// </summary>
-        protected Guid _id;
-        /// <summary>
-        /// Gets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        public virtual Guid Id
-        {
-            get
-            {
-                if (_id == Guid.Empty) _id = GetType().FullName.GetMD5();
-                return _id;
-            }
-        }
+        protected readonly Guid Id;
 
         /// <summary>
         /// Supportses the specified item.
@@ -105,6 +93,7 @@ namespace MediaBrowser.Controller.Providers
             Logger = logManager.GetLogger(GetType().Name);
             LogManager = logManager;
             ConfigurationManager = configurationManager;
+            Id = GetType().FullName.GetMD5();
 
             Initialize();
         }
@@ -130,8 +119,14 @@ namespace MediaBrowser.Controller.Providers
             {
                 throw new ArgumentNullException("item");
             }
-            
-            var data = item.ProviderData.GetValueOrDefault(Id, new BaseProviderInfo { ProviderId = Id });
+
+            BaseProviderInfo data;
+
+            if (!item.ProviderData.TryGetValue(Id, out data))
+            {
+                data = new BaseProviderInfo();
+            }
+
             data.LastRefreshed = value;
             data.LastRefreshStatus = status;
             data.ProviderVersion = providerVersion;
@@ -155,7 +150,7 @@ namespace MediaBrowser.Controller.Providers
         {
             SetLastRefreshed(item, value, ProviderVersion, status);
         }
-        
+
         /// <summary>
         /// Returns whether or not this provider should be re-fetched.  Default functionality can
         /// compare a provided date with a last refresh time.  This can be overridden for more complex
@@ -171,9 +166,14 @@ namespace MediaBrowser.Controller.Providers
                 throw new ArgumentNullException();
             }
 
-            var providerInfo = item.ProviderData.GetValueOrDefault(Id, new BaseProviderInfo());
+            BaseProviderInfo data;
 
-            return NeedsRefreshInternal(item, providerInfo);
+            if (!item.ProviderData.TryGetValue(Id, out data))
+            {
+                data = new BaseProviderInfo();
+            }
+
+            return NeedsRefreshInternal(item, data);
         }
 
         /// <summary>
@@ -194,7 +194,7 @@ namespace MediaBrowser.Controller.Providers
             {
                 throw new ArgumentNullException("providerInfo");
             }
-            
+
             if (CompareDate(item) > providerInfo.LastRefreshed)
             {
                 return true;
@@ -209,7 +209,7 @@ namespace MediaBrowser.Controller.Providers
             {
                 return true;
             }
-            
+
             return false;
         }
 
@@ -221,7 +221,7 @@ namespace MediaBrowser.Controller.Providers
         /// <returns><c>true</c> if [has file system stamp changed] [the specified item]; otherwise, <c>false</c>.</returns>
         protected bool HasFileSystemStampChanged(BaseItem item, BaseProviderInfo providerInfo)
         {
-            return GetCurrentFileSystemStamp(item) != providerInfo.FileSystemStamp;
+            return !string.Equals(GetCurrentFileSystemStamp(item), providerInfo.FileSystemStamp);
         }
 
         /// <summary>
@@ -279,7 +279,7 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         /// <param name="item">The item.</param>
         /// <returns>Guid.</returns>
-        private Guid GetCurrentFileSystemStamp(BaseItem item)
+        private string GetCurrentFileSystemStamp(BaseItem item)
         {
             if (UseParentFileSystemStamp(item) && item.Parent != null)
             {

+ 1 - 11
MediaBrowser.Controller/Providers/BaseProviderInfo.cs

@@ -7,11 +7,6 @@ namespace MediaBrowser.Controller.Providers
     /// </summary>
     public class BaseProviderInfo
     {
-        /// <summary>
-        /// Gets or sets the provider id.
-        /// </summary>
-        /// <value>The provider id.</value>
-        public Guid ProviderId { get; set; }
         /// <summary>
         /// Gets or sets the last refreshed.
         /// </summary>
@@ -21,7 +16,7 @@ namespace MediaBrowser.Controller.Providers
         /// Gets or sets the file system stamp.
         /// </summary>
         /// <value>The file system stamp.</value>
-        public Guid FileSystemStamp { get; set; }
+        public string FileSystemStamp { get; set; }
         /// <summary>
         /// Gets or sets the last refresh status.
         /// </summary>
@@ -32,11 +27,6 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         /// <value>The provider version.</value>
         public string ProviderVersion { get; set; }
-        /// <summary>
-        /// Gets or sets the data hash.
-        /// </summary>
-        /// <value>The data hash.</value>
-        public Guid DataHash { get; set; }
     }
 
     /// <summary>

+ 8 - 1
MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs

@@ -104,7 +104,14 @@ namespace MediaBrowser.Controller.Providers.Movies
             cancellationToken.ThrowIfCancellationRequested();
 
             var movie = item;
-            if (ShouldFetch(movie, movie.ProviderData.GetValueOrDefault(Id, new BaseProviderInfo { ProviderId = Id })))
+
+            BaseProviderInfo providerData;
+
+            if (!item.ProviderData.TryGetValue(Id, out providerData))
+            {
+                providerData = new BaseProviderInfo();
+            }
+            if (ShouldFetch(movie, providerData))
             {
                 var language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower();
                 var url = string.Format(FanArtBaseUrl, APIKey, movie.GetProviderId(MetadataProviders.Tmdb));

+ 13 - 5
MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs

@@ -22,10 +22,11 @@ namespace MediaBrowser.Controller.Providers.Movies
 {
     class MovieDbProviderException : ApplicationException
     {
-        public MovieDbProviderException(string msg) : base(msg)
+        public MovieDbProviderException(string msg)
+            : base(msg)
         {
         }
-     
+
     }
     /// <summary>
     /// Class MovieDbProvider
@@ -33,7 +34,7 @@ namespace MediaBrowser.Controller.Providers.Movies
     public class MovieDbProvider : BaseMetadataProvider, IDisposable
     {
         protected readonly IProviderManager ProviderManager;
-        
+
         /// <summary>
         /// The movie db
         /// </summary>
@@ -198,7 +199,7 @@ namespace MediaBrowser.Controller.Providers.Movies
                         base_url = "http://cf2.imgobject.com/t/p/"
 
                     }
-                }; 
+                };
             }
         }
 
@@ -223,7 +224,14 @@ namespace MediaBrowser.Controller.Providers.Movies
                 //in addition to ours, we need to set the last refreshed time for the local data provider
                 //so it won't see the new files we download and process them all over again
                 if (JsonProvider == null) JsonProvider = new MovieProviderFromJson(LogManager, ConfigurationManager, JsonSerializer, HttpClient, ProviderManager);
-                var data = item.ProviderData.GetValueOrDefault(JsonProvider.Id, new BaseProviderInfo { ProviderId = JsonProvider.Id });
+
+                BaseProviderInfo data;
+
+                if (!item.ProviderData.TryGetValue(JsonProvider.Id, out data))
+                {
+                    data = new BaseProviderInfo();
+                }
+
                 data.LastRefreshed = value;
                 item.ProviderData[JsonProvider.Id] = data;
             }

+ 25 - 3
MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Extensions;
+using System.Collections.Generic;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -52,14 +53,15 @@ namespace MediaBrowser.Controller.Providers.Music
 
             //Look at our parent for our album cover
             var artist = (MusicArtist)item.Parent;
-            var cover = artist.AlbumCovers != null ? artist.AlbumCovers.GetValueOrDefault(mbid, null) : null;
+
+            var cover = artist.AlbumCovers != null ? GetValueOrDefault(artist.AlbumCovers, mbid, null) : null;
             if (cover == null)
             {
                 // Not there - maybe it is new since artist last refreshed so refresh it and try again
                 await artist.RefreshMetadata(cancellationToken).ConfigureAwait(false);
                 cancellationToken.ThrowIfCancellationRequested();
 
-                cover = artist.AlbumCovers != null ? artist.AlbumCovers.GetValueOrDefault(mbid, null) : null;
+                cover = artist.AlbumCovers != null ? GetValueOrDefault(artist.AlbumCovers, mbid, null) : null;
             }
             if (cover == null)
             {
@@ -71,5 +73,25 @@ namespace MediaBrowser.Controller.Providers.Music
             item.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(item, cover, "folder.jpg", FanArtResourcePool, cancellationToken).ConfigureAwait(false));
             return true;
         }
+
+        /// <summary>
+        /// Helper method for Dictionaries since they throw on not-found keys
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <typeparam name="U"></typeparam>
+        /// <param name="dictionary">The dictionary.</param>
+        /// <param name="key">The key.</param>
+        /// <param name="defaultValue">The default value.</param>
+        /// <returns>``1.</returns>
+        private static U GetValueOrDefault<T, U>(Dictionary<T, U> dictionary, T key, U defaultValue)
+        {
+            U val;
+            if (!dictionary.TryGetValue(key, out val))
+            {
+                val = defaultValue;
+            }
+            return val;
+
+        }
     }
 }

+ 9 - 1
MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs

@@ -86,7 +86,15 @@ namespace MediaBrowser.Controller.Providers.Music
             cancellationToken.ThrowIfCancellationRequested();
 
             var artist = (MusicArtist)item;
-            if (ShouldFetch(artist, artist.ProviderData.GetValueOrDefault(Id, new BaseProviderInfo { ProviderId = Id })))
+
+            BaseProviderInfo providerData;
+
+            if (!item.ProviderData.TryGetValue(Id, out providerData))
+            {
+                providerData = new BaseProviderInfo();
+            }
+
+            if (ShouldFetch(artist, providerData))
             {
                 var url = string.Format(FanArtBaseUrl, APIKey, artist.GetProviderId(MetadataProviders.Musicbrainz));
                 var doc = new XmlDocument();

+ 7 - 1
MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs

@@ -229,7 +229,13 @@ namespace MediaBrowser.Controller.Providers.Music
 
             cancellationToken.ThrowIfCancellationRequested();
 
-            var providerData = item.ProviderData.GetValueOrDefault(Id, new BaseProviderInfo { ProviderId = Id });
+            BaseProviderInfo providerData;
+
+            if (!item.ProviderData.TryGetValue(Id, out providerData))
+            {
+                providerData = new BaseProviderInfo();
+            }
+            
             if (!ConfigurationManager.Configuration.SaveLocalMeta || !HasLocalMeta(item) || (force && !HasLocalMeta(item)) || (RefreshOnVersionChange && providerData.ProviderVersion != ProviderVersion))
             {
                 try

+ 9 - 1
MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs

@@ -60,7 +60,15 @@ namespace MediaBrowser.Controller.Providers.TV
             cancellationToken.ThrowIfCancellationRequested();
 
             var series = (Series)item;
-            if (ShouldFetch(series, series.ProviderData.GetValueOrDefault(Id, new BaseProviderInfo { ProviderId = Id })))
+
+            BaseProviderInfo providerData;
+
+            if (!item.ProviderData.TryGetValue(Id, out providerData))
+            {
+                providerData = new BaseProviderInfo();
+            }
+
+            if (ShouldFetch(series, providerData))
             {
                 string language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower();
                 string url = string.Format(FanArtBaseUrl, APIKey, series.GetProviderId(MetadataProviders.Tvdb));

+ 7 - 0
MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 
 namespace MediaBrowser.Controller.Sorting
 {
@@ -19,5 +20,11 @@ namespace MediaBrowser.Controller.Sorting
         /// </summary>
         /// <value>The user manager.</value>
         IUserManager UserManager { get; set; }
+
+        /// <summary>
+        /// Gets or sets the user data repository.
+        /// </summary>
+        /// <value>The user data repository.</value>
+        IUserDataRepository UserDataRepository { get; set; }
     }
 }

+ 0 - 28
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -153,27 +153,6 @@ namespace MediaBrowser.Model.Configuration
         [ProtoMember(52)]
         public bool DownloadHDFanArt { get; set; }
 
-        /// <summary>
-        /// Gets or sets the name of the item repository that should be used
-        /// </summary>
-        /// <value>The item repository.</value>
-        [ProtoMember(24)]
-        public string ItemRepository { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name of the user repository that should be used
-        /// </summary>
-        /// <value>The user repository.</value>
-        [ProtoMember(25)]
-        public string UserRepository { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name of the user data repository that should be used
-        /// </summary>
-        /// <value>The user data repository.</value>
-        [ProtoMember(26)]
-        public string UserDataRepository { get; set; }
-
         /// <summary>
         /// Characters to be replaced with a ' ' in strings to create a sort name
         /// </summary>
@@ -202,13 +181,6 @@ namespace MediaBrowser.Model.Configuration
         [ProtoMember(30)]
         public bool ShowLogWindow { get; set; }
 
-        /// <summary>
-        /// Gets or sets the name of the user data repository that should be used
-        /// </summary>
-        /// <value>The display preferences repository.</value>
-        [ProtoMember(31)]
-        public string DisplayPreferencesRepository { get; set; }
-
         /// <summary>
         /// The list of types that will NOT be allowed to have internet providers run against them even if they are turned on.
         /// </summary>

+ 1 - 1
MediaBrowser.Model/Connectivity/ClientConnectionInfo.cs

@@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Connectivity
         /// </summary>
         /// <value>The user id.</value>
         [ProtoMember(1)]
-        public Guid UserId { get; set; }
+        public string UserId { get; set; }
 
         /// <summary>
         /// Gets or sets the type of the client.

+ 1 - 1
MediaBrowser.Model/DTO/UserDto.cs

@@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Dto
         /// </summary>
         /// <value>The id.</value>
         [ProtoMember(2)]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
 
         /// <summary>
         /// Gets or sets the primary image tag.

+ 1 - 1
MediaBrowser.Model/Plugins/PluginInfo.cs

@@ -57,7 +57,7 @@ namespace MediaBrowser.Model.Plugins
         /// </summary>
         /// <value>The unique id.</value>
         [ProtoMember(9)]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
 
         /// <summary>
         /// Whether or not this plug-in should be automatically updated when a

+ 1 - 1
MediaBrowser.Model/Querying/ItemQuery.cs

@@ -12,7 +12,7 @@ namespace MediaBrowser.Model.Querying
         /// The user to localize search results for
         /// </summary>
         /// <value>The user id.</value>
-        public Guid UserId { get; set; }
+        public string UserId { get; set; }
 
         /// <summary>
         /// Specify this to localize the search to a specific item or folder. Omit to use the root.

+ 1 - 2
MediaBrowser.Model/Querying/ItemsByNameQuery.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Model.Entities;
-using System;
 
 namespace MediaBrowser.Model.Querying
 {
@@ -12,7 +11,7 @@ namespace MediaBrowser.Model.Querying
         /// Gets or sets the user id.
         /// </summary>
         /// <value>The user id.</value>
-        public Guid UserId { get; set; }
+        public string UserId { get; set; }
         /// <summary>
         /// Gets or sets the start index.
         /// </summary>

+ 1 - 1
MediaBrowser.Model/System/SystemInfo.cs

@@ -71,6 +71,6 @@ namespace MediaBrowser.Model.System
         /// </summary>
         /// <value>The id.</value>
         [ProtoMember(9)]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
     }
 }

+ 6 - 1
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -101,6 +101,8 @@ namespace MediaBrowser.Server.Implementations.Library
         /// </summary>
         private readonly IUserManager _userManager;
 
+        private readonly IUserDataRepository _userDataRepository;
+        
         /// <summary>
         /// Gets or sets the configuration manager.
         /// </summary>
@@ -136,12 +138,14 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <param name="taskManager">The task manager.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
-        public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager)
+        /// <param name="userDataRepository">The user data repository.</param>
+        public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataRepository userDataRepository)
         {
             _logger = logger;
             _taskManager = taskManager;
             _userManager = userManager;
             ConfigurationManager = configurationManager;
+            _userDataRepository = userDataRepository;
             ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>();
 
             ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
@@ -903,6 +907,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
                     userComparer.User = user;
                     userComparer.UserManager = _userManager;
+                    userComparer.UserDataRepository = _userDataRepository;
 
                     return userComparer;
                 }

+ 13 - 68
MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
@@ -86,20 +87,14 @@ namespace MediaBrowser.Server.Implementations.Library
         /// </summary>
         private readonly ILogger _logger;
 
+        private readonly IUserDataRepository _userDataRepository;
+        
         /// <summary>
         /// Gets or sets the configuration manager.
         /// </summary>
         /// <value>The configuration manager.</value>
         private IServerConfigurationManager ConfigurationManager { get; set; }
 
-        private readonly ConcurrentDictionary<string, Task<UserItemData>> _userData = new ConcurrentDictionary<string, Task<UserItemData>>();
-
-        /// <summary>
-        /// Gets the active user data repository
-        /// </summary>
-        /// <value>The user data repository.</value>
-        public IUserDataRepository UserDataRepository { get; set; }
-
         /// <summary>
         /// Gets the active user repository
         /// </summary>
@@ -321,13 +316,13 @@ namespace MediaBrowser.Server.Implementations.Library
 
             var connection = _activeConnections.GetOrAdd(key, keyName => new ClientConnectionInfo
             {
-                UserId = userId,
+                UserId = userId.ToString(),
                 Client = clientType,
                 DeviceName = deviceName,
                 DeviceId = deviceId
             });
 
-            connection.UserId = userId;
+            connection.UserId = userId.ToString();
             
             return connection;
         }
@@ -591,12 +586,14 @@ namespace MediaBrowser.Server.Implementations.Library
 
             UpdateNowPlayingItemId(user, clientType, deviceId, deviceName, item, positionTicks);
 
+            var key = item.GetUserDataKey();
+            
             if (positionTicks.HasValue)
             {
-                var data = await GetUserData(user.Id, item.UserDataId).ConfigureAwait(false);
+                var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
 
                 UpdatePlayState(item, data, positionTicks.Value, false);
-                await SaveUserData(user.Id, item.UserDataId, data, CancellationToken.None).ConfigureAwait(false);
+                await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
             }
 
             EventHelper.QueueEventIfNotNull(PlaybackProgress, this, new PlaybackProgressEventArgs
@@ -631,7 +628,9 @@ namespace MediaBrowser.Server.Implementations.Library
 
             RemoveNowPlayingItemId(user, clientType, deviceId, deviceName, item);
 
-            var data = await GetUserData(user.Id, item.UserDataId).ConfigureAwait(false);
+            var key = item.GetUserDataKey();
+
+            var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
 
             if (positionTicks.HasValue)
             {
@@ -644,7 +643,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 data.Played = true;
             }
 
-            await SaveUserData(user.Id, item.UserDataId, data, CancellationToken.None).ConfigureAwait(false);
+            await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
 
             EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackProgressEventArgs
             {
@@ -703,59 +702,5 @@ namespace MediaBrowser.Server.Implementations.Library
                 data.LastPlayedDate = DateTime.UtcNow;
             }
         }
-
-        /// <summary>
-        /// Saves display preferences for an item
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="userDataId">The user data id.</param>
-        /// <param name="userData">The user data.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public async Task SaveUserData(Guid userId, Guid userDataId, UserItemData userData, CancellationToken cancellationToken)
-        {
-            var key = userId + userDataId.ToString();
-            try
-            {
-                await UserDataRepository.SaveUserData(userId, userDataId, userData, cancellationToken).ConfigureAwait(false);
-
-                var newValue = Task.FromResult(userData);
-
-                // Once it succeeds, put it into the dictionary to make it available to everyone else
-                _userData.AddOrUpdate(key, newValue, delegate { return newValue; });
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error saving user data", ex);
-
-                throw;
-            }
-        }
-
-        /// <summary>
-        /// Gets the user data.
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="userDataId">The user data id.</param>
-        /// <returns>Task{UserItemData}.</returns>
-        public Task<UserItemData> GetUserData(Guid userId, Guid userDataId)
-        {
-            var key = userId + userDataId.ToString();
-
-            return _userData.GetOrAdd(key, keyName => RetrieveUserData(userId, userDataId));
-        }
-
-        /// <summary>
-        /// Retrieves the user data.
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="userDataId">The user data id.</param>
-        /// <returns>Task{UserItemData}.</returns>
-        private async Task<UserItemData> RetrieveUserData(Guid userId, Guid userDataId)
-        {
-            var userdata = await UserDataRepository.GetUserData(userId, userDataId).ConfigureAwait(false);
-
-            return userdata ?? new UserItemData();
-        }
     }
 }

+ 11 - 17
MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

@@ -88,12 +88,6 @@ namespace MediaBrowser.Server.Implementations.Providers
             Task.Run(() => ValidateCurrentlyRunningProviders());
         }
 
-        /// <summary>
-        /// Gets or sets the supported providers key.
-        /// </summary>
-        /// <value>The supported providers key.</value>
-        private Guid SupportedProvidersKey { get; set; }
-
         /// <summary>
         /// Adds the metadata providers.
         /// </summary>
@@ -103,6 +97,11 @@ namespace MediaBrowser.Server.Implementations.Providers
             MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
         }
 
+        /// <summary>
+        /// The _supported providers key
+        /// </summary>
+        private readonly Guid _supportedProvidersKey = "SupportedProviders".GetMD5();
+
         /// <summary>
         /// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence
         /// </summary>
@@ -126,19 +125,14 @@ namespace MediaBrowser.Server.Implementations.Providers
 
             BaseProviderInfo supportedProvidersInfo;
 
-            if (SupportedProvidersKey == Guid.Empty)
-            {
-                SupportedProvidersKey = "SupportedProviders".GetMD5();
-            }
-
-            var supportedProvidersHash = string.Join("+", supportedProviders.Select(i => i.GetType().Name)).GetMD5();
-            bool providersChanged = false;
+            var supportedProvidersValue = string.Join("+", supportedProviders.Select(i => i.GetType().Name));
+            var providersChanged = false;
 
-            item.ProviderData.TryGetValue(SupportedProvidersKey, out supportedProvidersInfo);
+            item.ProviderData.TryGetValue(_supportedProvidersKey, out supportedProvidersInfo);
             if (supportedProvidersInfo != null)
             {
                 // Force refresh if the supported providers have changed
-                providersChanged = force = force || supportedProvidersInfo.FileSystemStamp != supportedProvidersHash;
+                providersChanged = force = force || !string.Equals(supportedProvidersInfo.FileSystemStamp, supportedProvidersValue);
 
                 // If providers have changed, clear provider info and update the supported providers hash
                 if (providersChanged)
@@ -150,7 +144,7 @@ namespace MediaBrowser.Server.Implementations.Providers
 
             if (providersChanged)
             {
-                supportedProvidersInfo.FileSystemStamp = supportedProvidersHash;
+                supportedProvidersInfo.FileSystemStamp = supportedProvidersValue;
             }
 
             if (force) item.ClearMetaValues();
@@ -206,7 +200,7 @@ namespace MediaBrowser.Server.Implementations.Providers
 
             if (providersChanged)
             {
-                item.ProviderData[SupportedProvidersKey] = supportedProvidersInfo;
+                item.ProviderData[_supportedProvidersKey] = supportedProvidersInfo;
             }
             
             return result || providersChanged;

+ 8 - 1
MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Querying;
 using System;
@@ -23,6 +24,12 @@ namespace MediaBrowser.Server.Implementations.Sorting
         /// <value>The user manager.</value>
         public IUserManager UserManager { get; set; }
 
+        /// <summary>
+        /// Gets or sets the user data repository.
+        /// </summary>
+        /// <value>The user data repository.</value>
+        public IUserDataRepository UserDataRepository { get; set; }
+        
         /// <summary>
         /// Compares the specified x.
         /// </summary>
@@ -41,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
         /// <returns>DateTime.</returns>
         private DateTime GetDate(BaseItem x)
         {
-            var userdata = UserManager.GetUserData(User.Id, x.UserDataId).Result;
+            var userdata = UserDataRepository.GetUserData(User.Id, x.GetUserDataKey()).Result;
 
             if (userdata != null && userdata.LastPlayedDate.HasValue)
             {

+ 8 - 1
MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Querying;
 
@@ -34,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
         /// <returns>DateTime.</returns>
         private int GetValue(BaseItem x)
         {
-            var userdata = UserManager.GetUserData(User.Id, x.UserDataId).Result;
+            var userdata = UserDataRepository.GetUserData(User.Id, x.GetUserDataKey()).Result;
 
             return userdata == null ? 0 : userdata.PlayCount;
         }
@@ -48,6 +49,12 @@ namespace MediaBrowser.Server.Implementations.Sorting
             get { return ItemSortBy.PlayCount; }
         }
 
+        /// <summary>
+        /// Gets or sets the user data repository.
+        /// </summary>
+        /// <value>The user data repository.</value>
+        public IUserDataRepository UserDataRepository { get; set; }
+        
         /// <summary>
         /// Gets or sets the user manager.
         /// </summary>

+ 1 - 1
MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs

@@ -14,7 +14,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite
     /// <summary>
     /// Class SQLiteDisplayPreferencesRepository
     /// </summary>
-    class SQLiteDisplayPreferencesRepository : SqliteRepository, IDisplayPreferencesRepository
+    public class SQLiteDisplayPreferencesRepository : SqliteRepository, IDisplayPreferencesRepository
     {
         /// <summary>
         /// The repository name

+ 62 - 24
MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using System;
+using System.Collections.Concurrent;
 using System.Data;
 using System.IO;
 using System.Threading;
@@ -16,6 +17,8 @@ namespace MediaBrowser.Server.Implementations.Sqlite
     /// </summary>
     public class SQLiteUserDataRepository : SqliteRepository, IUserDataRepository
     {
+        private readonly ConcurrentDictionary<string, Task<UserItemData>> _userData = new ConcurrentDictionary<string, Task<UserItemData>>();
+        
         /// <summary>
         /// The repository name
         /// </summary>
@@ -45,9 +48,6 @@ namespace MediaBrowser.Server.Implementations.Sqlite
             }
         }
         
-        /// <summary>
-        /// The _protobuf serializer
-        /// </summary>
         private readonly IJsonSerializer _jsonSerializer;
 
         /// <summary>
@@ -90,8 +90,8 @@ namespace MediaBrowser.Server.Implementations.Sqlite
 
             string[] queries = {
 
-                                "create table if not exists userdata (id GUID, userId GUID, data BLOB)",
-                                "create unique index if not exists userdataindex on userdata (id, userId)",
+                                "create table if not exists userdata (key nvarchar, userId GUID, data BLOB)",
+                                "create unique index if not exists userdataindex on userdata (key, userId)",
                                 "create table if not exists schema_version (table_name primary key, version)",
                                 //pragmas
                                 "pragma temp_store = memory"
@@ -104,20 +104,18 @@ namespace MediaBrowser.Server.Implementations.Sqlite
         /// Saves the user data.
         /// </summary>
         /// <param name="userId">The user id.</param>
-        /// <param name="userDataId">The user data id.</param>
+        /// <param name="key">The key.</param>
         /// <param name="userData">The user data.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException">
-        /// userData
+        /// <exception cref="System.ArgumentNullException">userData
         /// or
         /// cancellationToken
         /// or
         /// userId
         /// or
-        /// userDataId
-        /// </exception>
-        public async Task SaveUserData(Guid userId, Guid userDataId, UserItemData userData, CancellationToken cancellationToken)
+        /// userDataId</exception>
+        public async Task SaveUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken)
         {
             if (userData == null)
             {
@@ -131,20 +129,49 @@ namespace MediaBrowser.Server.Implementations.Sqlite
             {
                 throw new ArgumentNullException("userId");
             }
-            if (userDataId == Guid.Empty)
+            if (string.IsNullOrEmpty(key))
             {
-                throw new ArgumentNullException("userDataId");
+                throw new ArgumentNullException("key");
             }
 
             cancellationToken.ThrowIfCancellationRequested();
 
+            try
+            {
+                await PersistUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
+
+                var newValue = Task.FromResult(userData);
+
+                // Once it succeeds, put it into the dictionary to make it available to everyone else
+                _userData.AddOrUpdate(key, newValue, delegate { return newValue; });
+            }
+            catch (Exception ex)
+            {
+                Logger.ErrorException("Error saving user data", ex);
+
+                throw;
+            }
+        }
+
+        /// <summary>
+        /// Persists the user data.
+        /// </summary>
+        /// <param name="userId">The user id.</param>
+        /// <param name="key">The key.</param>
+        /// <param name="userData">The user data.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public async Task PersistUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
             var serialized = _jsonSerializer.SerializeToBytes(userData);
 
             cancellationToken.ThrowIfCancellationRequested();
 
             var cmd = connection.CreateCommand();
-            cmd.CommandText = "replace into userdata (id, userId, data) values (@1, @2, @3)";
-            cmd.AddParam("@1", userDataId);
+            cmd.CommandText = "replace into userdata (key, userId, data) values (@1, @2, @3)";
+            cmd.AddParam("@1", key);
             cmd.AddParam("@2", userId);
             cmd.AddParam("@3", serialized);
 
@@ -174,29 +201,40 @@ namespace MediaBrowser.Server.Implementations.Sqlite
         /// Gets the user data.
         /// </summary>
         /// <param name="userId">The user id.</param>
-        /// <param name="userDataId">The user data id.</param>
+        /// <param name="key">The key.</param>
         /// <returns>Task{UserItemData}.</returns>
         /// <exception cref="System.ArgumentNullException">
         /// userId
         /// or
-        /// userDataId
+        /// key
         /// </exception>
-        public async Task<UserItemData> GetUserData(Guid userId, Guid userDataId)
+        public Task<UserItemData> GetUserData(Guid userId, string key)
         {
             if (userId == Guid.Empty)
             {
                 throw new ArgumentNullException("userId");
             }
-            if (userDataId == Guid.Empty)
+            if (string.IsNullOrEmpty(key))
             {
-                throw new ArgumentNullException("userDataId");
+                throw new ArgumentNullException("key");
             }
+            
+            return _userData.GetOrAdd(key, keyName => RetrieveUserData(userId, key));
+        }
 
+        /// <summary>
+        /// Retrieves the user data.
+        /// </summary>
+        /// <param name="userId">The user id.</param>
+        /// <param name="key">The key.</param>
+        /// <returns>Task{UserItemData}.</returns>
+        private async Task<UserItemData> RetrieveUserData(Guid userId, string key)
+        {
             var cmd = connection.CreateCommand();
-            cmd.CommandText = "select data from userdata where id = @id and userId=@userId";
+            cmd.CommandText = "select data from userdata where key = @key and userId=@userId";
 
-            var idParam = cmd.Parameters.Add("@id", DbType.Guid);
-            idParam.Value = userDataId;
+            var idParam = cmd.Parameters.Add("@key", DbType.Guid);
+            idParam.Value = key;
 
             var userIdParam = cmd.Parameters.Add("@userId", DbType.Guid);
             userIdParam.Value = userId;
@@ -212,7 +250,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite
                 }
             }
 
-            return null;
+            return new UserItemData();
         }
     }
 }

+ 17 - 19
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -37,6 +37,7 @@ using MediaBrowser.Server.Implementations.Library;
 using MediaBrowser.Server.Implementations.MediaEncoder;
 using MediaBrowser.Server.Implementations.Providers;
 using MediaBrowser.Server.Implementations.ServerManager;
+using MediaBrowser.Server.Implementations.Sqlite;
 using MediaBrowser.Server.Implementations.Udp;
 using MediaBrowser.Server.Implementations.Updates;
 using MediaBrowser.Server.Implementations.WebSocket;
@@ -152,6 +153,12 @@ namespace MediaBrowser.ServerApplication
         /// <value>The media encoder.</value>
         private IMediaEncoder MediaEncoder { get; set; }
 
+        /// <summary>
+        /// Gets or sets the user data repository.
+        /// </summary>
+        /// <value>The user data repository.</value>
+        private IUserDataRepository UserDataRepository { get; set; }
+
         /// <summary>
         /// The full path to our startmenu shortcut
         /// </summary>
@@ -216,7 +223,10 @@ namespace MediaBrowser.ServerApplication
             UserManager = new UserManager(Logger, ServerConfigurationManager);
             RegisterSingleInstance(UserManager);
 
-            LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager);
+            UserDataRepository = new SQLiteUserDataRepository(ApplicationPaths, JsonSerializer, LogManager);
+            RegisterSingleInstance(UserDataRepository);
+
+            LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataRepository);
             RegisterSingleInstance(LibraryManager);
 
             InstallationManager = new InstallationManager(HttpClient, PackageManager, JsonSerializer, Logger, this);
@@ -273,9 +283,7 @@ namespace MediaBrowser.ServerApplication
         /// <returns>Task.</returns>
         private async Task ConfigureDisplayPreferencesRepositories()
         {
-            var repositories = GetExports<IDisplayPreferencesRepository>();
-
-            var repository = GetRepository(repositories, ServerConfigurationManager.Configuration.DisplayPreferencesRepository);
+            var repository = new SQLiteDisplayPreferencesRepository(ApplicationPaths, JsonSerializer, LogManager);
 
             await repository.Initialize().ConfigureAwait(false);
 
@@ -288,9 +296,7 @@ namespace MediaBrowser.ServerApplication
         /// <returns>Task.</returns>
         private async Task ConfigureItemRepositories()
         {
-            var repositories = GetExports<IItemRepository>();
-
-            var repository = GetRepository(repositories, ServerConfigurationManager.Configuration.ItemRepository);
+            var repository = new SQLiteItemRepository(ApplicationPaths, JsonSerializer, LogManager);
 
             await repository.Initialize().ConfigureAwait(false);
 
@@ -301,22 +307,14 @@ namespace MediaBrowser.ServerApplication
         /// Configures the user data repositories.
         /// </summary>
         /// <returns>Task.</returns>
-        private async Task ConfigureUserDataRepositories()
+        private Task ConfigureUserDataRepositories()
         {
-            var repositories = GetExports<IUserDataRepository>();
-
-            var repository = GetRepository(repositories, ServerConfigurationManager.Configuration.UserDataRepository);
-
-            await repository.Initialize().ConfigureAwait(false);
-
-            ((UserManager)UserManager).UserDataRepository = repository;
+            return UserDataRepository.Initialize();
         }
 
         private async Task ConfigureUserRepositories()
         {
-            var repositories = GetExports<IUserRepository>();
-
-            var repository = GetRepository(repositories, ServerConfigurationManager.Configuration.UserRepository);
+            var repository = new SQLiteUserRepository(ApplicationPaths, JsonSerializer, LogManager);
 
             await repository.Initialize().ConfigureAwait(false);
 
@@ -470,7 +468,7 @@ namespace MediaBrowser.ServerApplication
             yield return GetType().Assembly;
         }
 
-        private readonly Guid _systemId = Environment.MachineName.GetMD5();
+        private readonly string _systemId = Environment.MachineName.GetMD5().ToString();
 
         /// <summary>
         /// Gets the system status.

+ 2 - 1
MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Plugins;
@@ -174,7 +175,7 @@ namespace MediaBrowser.ServerApplication.EntryPoints
         /// <param name="e">The e.</param>
         void userManager_UserUpdated(object sender, GenericEventArgs<User> e)
         {
-            var dto = new DtoBuilder(_logger, _libraryManager, _userManager).GetUserDto(e.Argument);
+            var dto = new UserDtoBuilder(_logger).GetUserDto(e.Argument);
 
             _serverManager.SendWebSocketMessage("UserUpdated", dto);
         }

+ 3 - 2
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Model.Logging;
@@ -192,9 +193,9 @@ namespace MediaBrowser.WebDashboard.Api
         {
             var connections = userManager.RecentConnections.ToArray();
 
-            var dtoBuilder = new DtoBuilder(logger, libraryManager, userManager);
+            var dtoBuilder = new UserDtoBuilder(logger);
 
-            var users = userManager.Users.Where(u => connections.Any(c => c.UserId == u.Id)).Select(dtoBuilder.GetUserDto);
+            var users = userManager.Users.Where(u => connections.Any(c => new Guid(c.UserId) == u.Id)).Select(dtoBuilder.GetUserDto);
 
             return new DashboardInfo
             {