From 785deff188ba51243739b827dbe42b5645404367 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 13 Apr 2013 14:02:30 -0400 Subject: removed excess hashing in providers and made user data key-based --- MediaBrowser.Api/Images/ImageService.cs | 1 + MediaBrowser.Api/Library/LibraryService.cs | 18 +- MediaBrowser.Api/Playback/BaseStreamingService.cs | 1 + .../UserLibrary/BaseItemsByNameService.cs | 36 +- MediaBrowser.Api/UserLibrary/GenresService.cs | 68 +- MediaBrowser.Api/UserLibrary/ItemsService.cs | 54 +- MediaBrowser.Api/UserLibrary/PersonsService.cs | 65 +- MediaBrowser.Api/UserLibrary/StudiosService.cs | 65 +- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 43 +- MediaBrowser.Api/UserLibrary/YearsService.cs | 7 +- MediaBrowser.Api/UserService.cs | 11 +- MediaBrowser.Common/Extensions/BaseExtensions.cs | 22 - MediaBrowser.Common/Plugins/BasePlugin.cs | 2 +- MediaBrowser.Controller/Dto/DtoBuilder.cs | 912 ++++++++++++++++++++ MediaBrowser.Controller/Dto/UserDtoBuilder.cs | 71 ++ MediaBrowser.Controller/Entities/BaseItem.cs | 43 +- MediaBrowser.Controller/Entities/Folder.cs | 3 +- MediaBrowser.Controller/Entities/Genre.cs | 8 + MediaBrowser.Controller/Entities/Movies/Movie.cs | 21 +- MediaBrowser.Controller/Entities/Person.cs | 8 + MediaBrowser.Controller/Entities/Studio.cs | 8 + MediaBrowser.Controller/Entities/TV/Episode.cs | 26 +- MediaBrowser.Controller/Entities/TV/Season.cs | 25 +- MediaBrowser.Controller/Entities/TV/Series.cs | 17 +- MediaBrowser.Controller/Entities/Year.cs | 8 + MediaBrowser.Controller/Library/DtoBuilder.cs | 952 --------------------- MediaBrowser.Controller/Library/IUserManager.cs | 19 - .../MediaBrowser.Controller.csproj | 3 +- .../Persistence/IUserDataRepository.cs | 12 +- .../Providers/BaseMetadataProvider.cs | 44 +- .../Providers/BaseProviderInfo.cs | 12 +- .../Providers/Movies/FanArtMovieProvider.cs | 9 +- .../Providers/Movies/MovieDbProvider.cs | 18 +- .../Providers/Music/FanArtAlbumProvider.cs | 28 +- .../Providers/Music/FanArtArtistProvider.cs | 10 +- .../Providers/Music/LastfmBaseProvider.cs | 8 +- .../Providers/TV/FanArtTVProvider.cs | 10 +- .../Sorting/IUserBaseItemComparer.cs | 7 + .../Configuration/ServerConfiguration.cs | 28 - .../Connectivity/ClientConnectionInfo.cs | 2 +- MediaBrowser.Model/DTO/UserDto.cs | 2 +- MediaBrowser.Model/Plugins/PluginInfo.cs | 2 +- MediaBrowser.Model/Querying/ItemQuery.cs | 2 +- MediaBrowser.Model/Querying/ItemsByNameQuery.cs | 3 +- MediaBrowser.Model/System/SystemInfo.cs | 2 +- .../Library/LibraryManager.cs | 7 +- .../Library/UserManager.cs | 81 +- .../Providers/ProviderManager.cs | 28 +- .../Sorting/DatePlayedComparer.cs | 9 +- .../Sorting/PlayCountComparer.cs | 9 +- .../Sqlite/SQLiteDisplayPreferencesRepository.cs | 2 +- .../Sqlite/SQLiteUserDataRepository.cs | 86 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 36 +- .../EntryPoints/WebSocketEvents.cs | 3 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 5 +- 55 files changed, 1408 insertions(+), 1574 deletions(-) create mode 100644 MediaBrowser.Controller/Dto/DtoBuilder.cs create mode 100644 MediaBrowser.Controller/Dto/UserDtoBuilder.cs delete mode 100644 MediaBrowser.Controller/Library/DtoBuilder.cs diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index cb26688e39..07bbaff749 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/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; diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 9c68df7712..5b34119dea 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/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 /// private readonly IApplicationHost _appHost; private readonly ILibraryManager _libraryManager; - private readonly IUserManager _userManager; + private readonly IUserDataRepository _userDataRepository; /// /// Initializes a new instance of the class. /// /// The app host. /// The library manager. - /// The user manager. + /// The user data repository. /// appHost - 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; } /// @@ -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); } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 3c6731e71f..1510dd472f 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/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; diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 6801e14f0a..42aafd2cd2 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/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 /// protected readonly ILibraryManager LibraryManager; + protected readonly IUserDataRepository UserDataRepository; /// /// Initializes a new instance of the class. /// /// The user manager. /// The library manager. - protected BaseItemsByNameService(IUserManager userManager, ILibraryManager libraryManager) + /// The user data repository. + protected BaseItemsByNameService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository) { UserManager = userManager; LibraryManager = libraryManager; + UserDataRepository = userDataRepository; } /// @@ -132,18 +137,19 @@ namespace MediaBrowser.Api.UserLibrary /// IEnumerable{BaseItem}. private IEnumerable FilterItems(GetItemsByName request, IEnumerable 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); } } diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index 54561f4005..e275b6ed03 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/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 - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - [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 - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid UserId { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Name { get; set; } - } /// /// Class GenresService /// public class GenresService : BaseItemsByNameService { - 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); } - /// - /// Posts the specified request. - /// - /// The request. - public void Post(MarkFavoriteGenre request) - { - var task = MarkFavorite(() => LibraryManager.GetGenre(request.Name), request.UserId, true); - - Task.WaitAll(task); - } - - /// - /// Deletes the specified request. - /// - /// The request. - public void Delete(UnmarkFavoriteGenre request) - { - var task = MarkFavorite(() => LibraryManager.GetGenre(request.Name), request.UserId, false); - - Task.WaitAll(task); - } - /// /// Gets all items. /// diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index aed84e07f2..675ac0cd55 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/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 /// private readonly IUserManager _userManager; + private readonly IUserDataRepository _userDataRepository; /// /// The _library manager @@ -161,11 +164,13 @@ namespace MediaBrowser.Api.UserLibrary /// The user manager. /// The library manager. /// The search engine. - public ItemsService(IUserManager userManager, ILibraryManager libraryManager, ILibrarySearchEngine searchEngine) + /// The user data repository. + public ItemsService(IUserManager userManager, ILibraryManager libraryManager, ILibrarySearchEngine searchEngine, IUserDataRepository userDataRepository) { _userManager = userManager; _libraryManager = libraryManager; _searchEngine = searchEngine; + _userDataRepository = userDataRepository; } /// @@ -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 /// The items. /// The filter. /// The user. - /// The user manager. + /// The repository. /// IEnumerable{BaseItem}. - internal static IEnumerable ApplyFilter(IEnumerable items, ItemFilter filter, User user, IUserManager userManager) + internal static IEnumerable ApplyFilter(IEnumerable 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 /// /// Applies the additional filters. /// - /// The items request. + /// The request. /// The items. /// IEnumerable{BaseItem}. - internal static IEnumerable ApplyAdditionalFilters(BaseItemsRequest itemsRequest, IEnumerable items) + internal static IEnumerable ApplyAdditionalFilters(GetItems request, IEnumerable 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)) { diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index 4253ddc804..974b8c0027 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/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 /// The person types. public string PersonTypes { get; set; } } - - [Route("/Users/{UserId}/FavoritePersons/{Name}", "POST")] - [Api(Description = "Marks a person as a favorite")] - public class MarkFavoritePerson : IReturnVoid - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - [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 - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid UserId { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Name { get; set; } - } /// /// Class PersonsService /// public class PersonsService : BaseItemsByNameService { - 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); } - /// - /// Posts the specified request. - /// - /// The request. - public void Post(MarkFavoritePerson request) - { - var task = MarkFavorite(() => LibraryManager.GetPerson(request.Name), request.UserId, true); - - Task.WaitAll(task); - } - - /// - /// Deletes the specified request. - /// - /// The request. - public void Delete(UnmarkFavoritePerson request) - { - var task = MarkFavorite(() => LibraryManager.GetPerson(request.Name), request.UserId, false); - - Task.WaitAll(task); - } - /// /// Gets all items. /// diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index 24f09c5ef7..77f20d8e85 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/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 - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - [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 - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid UserId { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Name { get; set; } - } /// /// Class StudiosService /// public class StudiosService : BaseItemsByNameService { - 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); } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(MarkFavoriteStudio request) - { - var task = MarkFavorite(() => LibraryManager.GetStudio(request.Name), request.UserId, true); - - Task.WaitAll(task); - } - - /// - /// Deletes the specified request. - /// - /// The request. - public void Delete(UnmarkFavoriteStudio request) - { - var task = MarkFavorite(() => LibraryManager.GetStudio(request.Name), request.UserId, false); - - Task.WaitAll(task); - } /// /// Gets all items. diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index e24e638196..70e5287a1c 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/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 /// private readonly IUserManager _userManager; - + private readonly IUserDataRepository _userDataRepository; private readonly ILibraryManager _libraryManager; /// /// Initializes a new instance of the class. /// /// jsonSerializer - public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager) + public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository) : base() { _userManager = userManager; _libraryManager = libraryManager; + _userDataRepository = userDataRepository; } /// @@ -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); } } } diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index f2cf367507..481645c242 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/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 /// 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) { } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 76c67cd2f8..3f3af743d1 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/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 /// System.Object. 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); } diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs index 9591862e76..b4ad901689 100644 --- a/MediaBrowser.Common/Extensions/BaseExtensions.cs +++ b/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(); } - /// - /// Helper method for Dictionaries since they throw on not-found keys - /// - /// - /// - /// The dictionary. - /// The key. - /// The default value. - /// ``1. - public static U GetValueOrDefault(this Dictionary dictionary, T key, U defaultValue) - { - U val; - if (!dictionary.TryGetValue(key, out val)) - { - val = defaultValue; - } - return val; - - } - /// /// Gets the attribute value. /// diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index ca2891734e..f90cd2c0fe 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/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 diff --git a/MediaBrowser.Controller/Dto/DtoBuilder.cs b/MediaBrowser.Controller/Dto/DtoBuilder.cs new file mode 100644 index 0000000000..0da4da356b --- /dev/null +++ b/MediaBrowser.Controller/Dto/DtoBuilder.cs @@ -0,0 +1,912 @@ +using MediaBrowser.Common.Extensions; +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; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Dto +{ + /// + /// Generates DTO's from domain entities + /// + public class DtoBuilder + { + /// + /// The index folder delimeter + /// + const string IndexFolderDelimeter = "-index-"; + + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly IUserDataRepository _userDataRepository; + + public DtoBuilder(ILogger logger, ILibraryManager libraryManager, IUserDataRepository userDataRepository) + { + _logger = logger; + _libraryManager = libraryManager; + _userDataRepository = userDataRepository; + } + + /// + /// Gets the dto base item. + /// + /// The item. + /// The fields. + /// Task{DtoBaseItem}. + /// item + public async Task GetBaseItemDto(BaseItem item, List fields) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + if (fields == null) + { + throw new ArgumentNullException("fields"); + } + + var dto = new BaseItemDto(); + + var tasks = new List(); + + if (fields.Contains(ItemFields.Studios)) + { + dto.Studios = item.Studios; + } + + if (fields.Contains(ItemFields.People)) + { + tasks.Add(AttachPeople(dto, item)); + } + + if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) + { + try + { + AttachPrimaryImageAspectRatio(dto, item, _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, item.Name); + } + } + + AttachBasicFields(dto, item, fields); + + // Make sure all the tasks we kicked off have completed. + if (tasks.Count > 0) + { + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + return dto; + } + + /// + /// Converts a BaseItem to a DTOBaseItem + /// + /// The item. + /// The user. + /// The fields. + /// Task{DtoBaseItem}. + /// item + public async Task GetBaseItemDto(BaseItem item, User user, List fields) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + if (user == null) + { + throw new ArgumentNullException("user"); + } + if (fields == null) + { + throw new ArgumentNullException("fields"); + } + + var dto = new BaseItemDto(); + + var tasks = new List(); + + if (fields.Contains(ItemFields.Studios)) + { + dto.Studios = item.Studios; + } + + if (fields.Contains(ItemFields.People)) + { + tasks.Add(AttachPeople(dto, item)); + } + + tasks.Add(AttachUserSpecificInfo(dto, item, user, fields)); + + if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) + { + try + { + AttachPrimaryImageAspectRatio(dto, item, _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, item.Name); + } + } + + AttachBasicFields(dto, item, fields); + + // Make sure all the tasks we kicked off have completed. + if (tasks.Count > 0) + { + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + return dto; + } + + /// + /// Attaches the user specific info. + /// + /// The dto. + /// The item. + /// The user. + /// The fields. + private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List fields) + { + if (fields.Contains(ItemFields.UserData)) + { + var userData = await _userDataRepository.GetUserData(user.Id, item.GetUserDataKey()).ConfigureAwait(false); + + dto.UserData = GetUserItemDataDto(userData); + } + + if (item.IsFolder && fields.Contains(ItemFields.DisplayPreferencesId)) + { + dto.DisplayPreferencesId = ((Folder) item).GetDisplayPreferencesId(user.Id).ToString(); + } + + if (item.IsFolder) + { + if (fields.Contains(ItemFields.ItemCounts)) + { + var folder = (Folder)item; + + // Skip sorting since all we want is a count + dto.ChildCount = folder.GetChildren(user).Count(); + + await SetSpecialCounts(folder, user, dto, _userDataRepository).ConfigureAwait(false); + } + } + } + + /// + /// Attaches the primary image aspect ratio. + /// + /// The dto. + /// The item. + /// The _logger. + /// Task. + internal static void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item, ILogger _logger) + { + var path = item.PrimaryImagePath; + + if (string.IsNullOrEmpty(path)) + { + return; + } + + var metaFileEntry = item.ResolveArgs.GetMetaFileByPath(path); + + // See if we can avoid a file system lookup by looking for the file in ResolveArgs + var dateModified = metaFileEntry == null ? File.GetLastWriteTimeUtc(path) : metaFileEntry.Value.LastWriteTimeUtc; + + ImageSize size; + + try + { + size = Kernel.Instance.ImageManager.GetImageSize(path, dateModified); + } + catch (FileNotFoundException) + { + _logger.Error("Image file does not exist: {0}", path); + return; + } + catch (Exception ex) + { + _logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path); + return; + } + + foreach (var enhancer in Kernel.Instance.ImageEnhancers + .Where(i => i.Supports(item, ImageType.Primary))) + { + + size = enhancer.GetEnhancedImageSize(item, ImageType.Primary, 0, size); + } + + dto.PrimaryImageAspectRatio = size.Width / size.Height; + } + + /// + /// Sets simple property values on a DTOBaseItem + /// + /// The dto. + /// The item. + /// The fields. + private void AttachBasicFields(BaseItemDto dto, BaseItem item, List fields) + { + if (fields.Contains(ItemFields.DateCreated)) + { + dto.DateCreated = item.DateCreated; + } + + if (fields.Contains(ItemFields.DisplayMediaType)) + { + dto.DisplayMediaType = item.DisplayMediaType; + } + + if (fields.Contains(ItemFields.Budget)) + { + dto.Budget = item.Budget; + } + + if (fields.Contains(ItemFields.EndDate)) + { + dto.EndDate = item.EndDate; + } + + if (fields.Contains(ItemFields.HomePageUrl)) + { + dto.HomePageUrl = item.HomePageUrl; + } + + if (fields.Contains(ItemFields.ProductionLocations)) + { + dto.ProductionLocations = item.ProductionLocations; + } + + dto.AspectRatio = item.AspectRatio; + + dto.BackdropImageTags = GetBackdropImageTags(item); + + if (fields.Contains(ItemFields.Genres)) + { + dto.Genres = item.Genres; + } + + if (item.Images != null) + { + dto.ImageTags = new Dictionary(); + + foreach (var image in item.Images) + { + ImageType type; + + if (Enum.TryParse(image.Key, true, out type)) + { + dto.ImageTags[type] = Kernel.Instance.ImageManager.GetImageCacheTag(item, type, image.Value); + } + } + } + + dto.Id = GetClientItemId(item); + dto.IndexNumber = item.IndexNumber; + dto.IsFolder = item.IsFolder; + dto.Language = item.Language; + dto.MediaType = item.MediaType; + dto.LocationType = item.LocationType; + + var localTrailerCount = item.LocalTrailers == null ? 0 : item.LocalTrailers.Count; + + if (localTrailerCount > 0) + { + dto.LocalTrailerCount = localTrailerCount; + } + + dto.Name = item.Name; + dto.OfficialRating = item.OfficialRating; + + var hasOverview = fields.Contains(ItemFields.Overview); + var hasHtmlOverview = fields.Contains(ItemFields.OverviewHtml); + + if (hasOverview || hasHtmlOverview) + { + var strippedOverview = string.IsNullOrEmpty(item.Overview) ? item.Overview : item.Overview.StripHtml(); + + if (hasOverview) + { + dto.Overview = strippedOverview; + } + + // Only supply the html version if there was actually html content + if (hasHtmlOverview) + { + dto.OverviewHtml = item.Overview; + } + } + + // If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance + if (dto.BackdropImageTags.Count == 0) + { + var parentWithBackdrop = GetParentBackdropItem(item); + + if (parentWithBackdrop != null) + { + dto.ParentBackdropItemId = GetClientItemId(parentWithBackdrop); + dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop); + } + } + + if (item.Parent != null && fields.Contains(ItemFields.ParentId)) + { + dto.ParentId = GetClientItemId(item.Parent); + } + + dto.ParentIndexNumber = item.ParentIndexNumber; + + // If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance + if (!dto.HasLogo) + { + var parentWithLogo = GetParentLogoItem(item); + + if (parentWithLogo != null) + { + dto.ParentLogoItemId = GetClientItemId(parentWithLogo); + + dto.ParentLogoImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImage(ImageType.Logo)); + } + } + + if (fields.Contains(ItemFields.Path)) + { + dto.Path = item.Path; + } + + dto.PremiereDate = item.PremiereDate; + dto.ProductionYear = item.ProductionYear; + + if (fields.Contains(ItemFields.ProviderIds)) + { + dto.ProviderIds = item.ProviderIds; + } + + dto.RunTimeTicks = item.RunTimeTicks; + + if (fields.Contains(ItemFields.SortName)) + { + dto.SortName = item.SortName; + } + + if (fields.Contains(ItemFields.Taglines)) + { + dto.Taglines = item.Taglines; + } + + if (fields.Contains(ItemFields.TrailerUrls)) + { + dto.TrailerUrls = item.TrailerUrls; + } + + dto.Type = item.GetType().Name; + dto.CommunityRating = item.CommunityRating; + + if (item.IsFolder) + { + var folder = (Folder)item; + + if (fields.Contains(ItemFields.IndexOptions)) + { + dto.IndexOptions = folder.IndexByOptionStrings.ToArray(); + } + } + + // Add audio info + var audio = item as Audio; + if (audio != null) + { + if (fields.Contains(ItemFields.AudioInfo)) + { + dto.Album = audio.Album; + dto.AlbumArtist = audio.AlbumArtist; + dto.Artist = audio.Artist; + } + } + + // Add video info + var video = item as Video; + if (video != null) + { + dto.VideoType = video.VideoType; + dto.VideoFormat = video.VideoFormat; + dto.IsoType = video.IsoType; + + if (fields.Contains(ItemFields.Chapters) && video.Chapters != null) + { + dto.Chapters = video.Chapters.Select(c => GetChapterInfoDto(c, item)).ToList(); + } + } + + if (fields.Contains(ItemFields.MediaStreams)) + { + // Add VideoInfo + var iHasMediaStreams = item as IHasMediaStreams; + + if (iHasMediaStreams != null) + { + dto.MediaStreams = iHasMediaStreams.MediaStreams; + } + } + + // Add MovieInfo + var movie = item as Movie; + + if (movie != null) + { + var specialFeatureCount = movie.SpecialFeatures == null ? 0 : movie.SpecialFeatures.Count; + + if (specialFeatureCount > 0) + { + dto.SpecialFeatureCount = specialFeatureCount; + } + } + + if (fields.Contains(ItemFields.SeriesInfo)) + { + // Add SeriesInfo + var series = item as Series; + + if (series != null) + { + dto.AirDays = series.AirDays; + dto.AirTime = series.AirTime; + dto.Status = series.Status; + } + + // Add EpisodeInfo + var episode = item as Episode; + + if (episode != null) + { + series = item.FindParent(); + + dto.SeriesId = GetClientItemId(series); + dto.SeriesName = series.Name; + } + + // Add SeasonInfo + var season = item as Season; + + if (season != null) + { + series = item.FindParent(); + + dto.SeriesId = GetClientItemId(series); + dto.SeriesName = series.Name; + } + } + } + + /// + /// Since it can be slow to make all of these calculations independently, this method will provide a way to do them all at once + /// + /// The folder. + /// The user. + /// The dto. + /// The user data repository. + /// Task. + private static async Task SetSpecialCounts(Folder folder, User user, BaseItemDto dto, IUserDataRepository userDataRepository) + { + var rcentlyAddedItemCount = 0; + var recursiveItemCount = 0; + + double totalPercentPlayed = 0; + + // Loop through each recursive child + foreach (var child in folder.GetRecursiveChildren(user).Where(i => !i.IsFolder)) + { + var userdata = await userDataRepository.GetUserData(user.Id, child.GetUserDataKey()).ConfigureAwait(false); + + recursiveItemCount++; + + // Check is recently added + if (child.IsRecentlyAdded(user)) + { + rcentlyAddedItemCount++; + } + + // Incrememt totalPercentPlayed + if (userdata != null) + { + if (userdata.PlayCount > 0) + { + totalPercentPlayed += 100; + } + else if (userdata.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0) + { + double itemPercent = userdata.PlaybackPositionTicks; + itemPercent /= child.RunTimeTicks.Value; + totalPercentPlayed += itemPercent; + } + } + } + + dto.RecursiveItemCount = recursiveItemCount; + dto.RecentlyAddedItemCount = rcentlyAddedItemCount; + + if (recursiveItemCount > 0) + { + dto.PlayedPercentage = totalPercentPlayed / recursiveItemCount; + } + } + + /// + /// Attaches People DTO's to a DTOBaseItem + /// + /// The dto. + /// The item. + /// Task. + private async Task AttachPeople(BaseItemDto dto, BaseItem item) + { + if (item.People == null) + { + return; + } + + // Attach People by transforming them into BaseItemPerson (DTO) + dto.People = new BaseItemPerson[item.People.Count]; + + // Ordering by person type to ensure actors and artists are at the front. + // This is taking advantage of the fact that they both begin with A + // This should be improved in the future + var entities = await Task.WhenAll(item.People.OrderBy(i => i.Type).Select(c => + + Task.Run(async () => + { + try + { + return await _libraryManager.GetPerson(c.Name).ConfigureAwait(false); + } + catch (IOException ex) + { + _logger.ErrorException("Error getting person {0}", ex, c.Name); + return null; + } + }) + + )).ConfigureAwait(false); + + for (var i = 0; i < item.People.Count; i++) + { + var person = item.People[i]; + + var baseItemPerson = new BaseItemPerson + { + Name = person.Name, + Role = person.Role, + Type = person.Type + }; + + var ibnObject = entities[i]; + + if (ibnObject != null) + { + var primaryImagePath = ibnObject.PrimaryImagePath; + + if (!string.IsNullOrEmpty(primaryImagePath)) + { + baseItemPerson.PrimaryImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(ibnObject, ImageType.Primary, primaryImagePath); + } + } + + dto.People[i] = baseItemPerson; + } + } + + /// + /// If an item does not any backdrops, this can be used to find the first parent that does have one + /// + /// The item. + /// BaseItem. + private BaseItem GetParentBackdropItem(BaseItem item) + { + var parent = item.Parent; + + while (parent != null) + { + if (parent.BackdropImagePaths != null && parent.BackdropImagePaths.Count > 0) + { + return parent; + } + + parent = parent.Parent; + } + + return null; + } + + /// + /// If an item does not have a logo, this can be used to find the first parent that does have one + /// + /// The item. + /// BaseItem. + private BaseItem GetParentLogoItem(BaseItem item) + { + var parent = item.Parent; + + while (parent != null) + { + if (parent.HasImage(ImageType.Logo)) + { + return parent; + } + + parent = parent.Parent; + } + + return null; + } + + /// + /// Gets the library update info. + /// + /// The instance containing the event data. + /// LibraryUpdateInfo. + public static LibraryUpdateInfo GetLibraryUpdateInfo(ChildrenChangedEventArgs changeEvent) + { + return new LibraryUpdateInfo + { + Folder = GetBaseItemInfo(changeEvent.Folder), + ItemsAdded = changeEvent.ItemsAdded.Select(GetBaseItemInfo), + ItemsRemoved = changeEvent.ItemsRemoved.Select(i => i.Id), + ItemsUpdated = changeEvent.ItemsUpdated.Select(i => i.Id) + }; + } + + /// + /// Converts a UserItemData to a DTOUserItemData + /// + /// The data. + /// DtoUserItemData. + /// + public UserItemDataDto GetUserItemDataDto(UserItemData data) + { + if (data == null) + { + throw new ArgumentNullException(); + } + + return new UserItemDataDto + { + IsFavorite = data.IsFavorite, + Likes = data.Likes, + PlaybackPositionTicks = data.PlaybackPositionTicks, + PlayCount = data.PlayCount, + Rating = data.Rating, + Played = data.Played, + LastPlayedDate = data.LastPlayedDate + }; + } + + /// + /// Gets the chapter info dto. + /// + /// The chapter info. + /// The item. + /// ChapterInfoDto. + private ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item) + { + var dto = new ChapterInfoDto + { + Name = chapterInfo.Name, + StartPositionTicks = chapterInfo.StartPositionTicks + }; + + if (!string.IsNullOrEmpty(chapterInfo.ImagePath)) + { + dto.ImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Chapter, chapterInfo.ImagePath); + } + + return dto; + } + + /// + /// Converts a BaseItem to a BaseItemInfo + /// + /// The item. + /// BaseItemInfo. + /// item + public static BaseItemInfo GetBaseItemInfo(BaseItem item) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + var info = new BaseItemInfo + { + Id = GetClientItemId(item), + Name = item.Name, + Type = item.GetType().Name, + IsFolder = item.IsFolder, + RunTimeTicks = item.RunTimeTicks + }; + + var imagePath = item.PrimaryImagePath; + + if (!string.IsNullOrEmpty(imagePath)) + { + info.PrimaryImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Primary, imagePath); + } + + if (item.BackdropImagePaths != null && item.BackdropImagePaths.Count > 0) + { + imagePath = item.BackdropImagePaths[0]; + + if (!string.IsNullOrEmpty(imagePath)) + { + info.BackdropImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Backdrop, imagePath); + } + } + + return info; + } + + /// + /// Gets client-side Id of a server-side BaseItem + /// + /// The item. + /// System.String. + /// item + public static string GetClientItemId(BaseItem item) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + var indexFolder = item as IndexFolder; + + if (indexFolder != null) + { + return GetClientItemId(indexFolder.Parent) + IndexFolderDelimeter + (indexFolder.IndexName ?? string.Empty) + IndexFolderDelimeter + indexFolder.Id; + } + + return item.Id.ToString(); + } + + /// + /// Gets a BaseItem based upon it's client-side item id + /// + /// The id. + /// The user manager. + /// The library manager. + /// The user id. + /// BaseItem. + public static BaseItem GetItemByClientId(string id, IUserManager userManager, ILibraryManager libraryManager, Guid? userId = null) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } + + // If the item is an indexed folder we have to do a special routine to get it + var isIndexFolder = id.IndexOf(IndexFolderDelimeter, StringComparison.OrdinalIgnoreCase) != -1; + + if (isIndexFolder) + { + if (userId.HasValue) + { + return GetIndexFolder(id, userId.Value, userManager, libraryManager); + } + } + + BaseItem item = null; + + if (userId.HasValue || !isIndexFolder) + { + item = libraryManager.GetItemById(new Guid(id)); + } + + // If we still don't find it, look within individual user views + if (item == null && !userId.HasValue && isIndexFolder) + { + foreach (var user in userManager.Users) + { + item = GetItemByClientId(id, userManager, libraryManager, user.Id); + + if (item != null) + { + break; + } + } + } + + return item; + } + + /// + /// Finds an index folder based on an Id and userId + /// + /// The id. + /// The user id. + /// The user manager. + /// The library manager. + /// BaseItem. + private static BaseItem GetIndexFolder(string id, Guid userId, IUserManager userManager, ILibraryManager libraryManager) + { + var user = userManager.GetUserById(userId); + + var stringSeparators = new[] { IndexFolderDelimeter }; + + // Split using the delimeter + var values = id.Split(stringSeparators, StringSplitOptions.None).ToList(); + + // Get the top folder normally using the first id + var folder = GetItemByClientId(values[0], userManager, libraryManager, userId) as Folder; + + values.RemoveAt(0); + + // Get indexed folders using the remaining values in the id string + return GetIndexFolder(values, folder, user); + } + + /// + /// Gets indexed folders based on a list of index names and folder id's + /// + /// The values. + /// The parent folder. + /// The user. + /// BaseItem. + private static BaseItem GetIndexFolder(List values, Folder parentFolder, User user) + { + // The index name is first + var indexBy = values[0]; + + // The index folder id is next + var indexFolderId = new Guid(values[1]); + + // Remove them from the lst + values.RemoveRange(0, 2); + + // Get the IndexFolder + var indexFolder = parentFolder.GetChildren(user, indexBy).FirstOrDefault(i => i.Id == indexFolderId) as Folder; + + // Nested index folder + if (values.Count > 0) + { + return GetIndexFolder(values, indexFolder, user); + } + + return indexFolder; + } + + /// + /// Gets the backdrop image tags. + /// + /// The item. + /// List{System.String}. + private List GetBackdropImageTags(BaseItem item) + { + if (item.BackdropImagePaths == null) + { + return new List(); + } + + return item.BackdropImagePaths.Select(p => Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Backdrop, p)).ToList(); + } + } +} diff --git a/MediaBrowser.Controller/Dto/UserDtoBuilder.cs b/MediaBrowser.Controller/Dto/UserDtoBuilder.cs new file mode 100644 index 0000000000..5c717529ae --- /dev/null +++ b/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 +{ + /// + /// Class UserDtoBuilder + /// + public class UserDtoBuilder + { + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + public UserDtoBuilder(ILogger logger) + { + _logger = logger; + } + + /// + /// Converts a User to a DTOUser + /// + /// The user. + /// DtoUser. + /// user + 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; + } + } +} diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 2cb8ac7949..ccaabd4382 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/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 /// /// The _file system stamp /// - private Guid? _fileSystemStamp; + private string _fileSystemStamp; /// /// 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. /// /// The file system stamp. [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. /// /// Guid. - 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(); } /// @@ -820,21 +816,12 @@ namespace MediaBrowser.Controller.Entities } /// - /// The _user data id + /// Gets the user data key. /// - protected Guid _userDataId; //cache this so it doesn't have to be re-constructed on every reference - /// - /// 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. - /// - /// The user data id. - [IgnoreDataMember] - public virtual Guid UserDataId + /// System.String. + public virtual string GetUserDataKey() { - get - { - return _userDataId == Guid.Empty ? (_userDataId = Id) : _userDataId; - } + return Id.ToString(); } /// @@ -1151,14 +1138,16 @@ namespace MediaBrowser.Controller.Entities /// The user manager. /// Task. /// - 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); } /// diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index eefce2fd34..fed6bb7de3 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/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 /// if set to true [was played]. /// The user manager. /// Task. - 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); diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index d5e8afb202..619c7a12b6 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -6,5 +6,13 @@ namespace MediaBrowser.Controller.Entities /// public class Genre : BaseItem { + /// + /// Gets the user data key. + /// + /// System.String. + public override string GetUserDataKey() + { + return Name; + } } } diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index d085880775..b70ac2b3b8 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/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 } /// - /// Override to use tmdb or imdb id so it will stick if the item moves physical locations + /// Gets the user data key. /// - /// The user data id. - [IgnoreDataMember] - public override Guid UserDataId + /// System.String. + 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(); } /// diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 5c92c019b8..f5570448da 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -6,6 +6,14 @@ namespace MediaBrowser.Controller.Entities /// public class Person : BaseItem { + /// + /// Gets the user data key. + /// + /// System.String. + public override string GetUserDataKey() + { + return Name; + } } /// diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index a255849e61..06511d959e 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -6,5 +6,13 @@ namespace MediaBrowser.Controller.Entities /// public class Studio : BaseItem { + /// + /// Gets the user data key. + /// + /// System.String. + public override string GetUserDataKey() + { + return Name; + } } } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 7588e4e692..dd1434cea6 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -49,27 +49,19 @@ namespace MediaBrowser.Controller.Entities.TV } /// - /// Override to use the provider Ids + season and episode number so it will be portable + /// Gets the user data key. /// - /// The user data id. - [IgnoreDataMember] - public override Guid UserDataId + /// System.String. + 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(); } /// diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 2f2bee1721..2f3f7387ca 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -67,27 +67,18 @@ namespace MediaBrowser.Controller.Entities.TV } /// - /// Override to use the provider Ids + season number so it will be portable + /// Gets the user data key. /// - /// The user data id. - [IgnoreDataMember] - public override Guid UserDataId + /// System.String. + 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(); } /// diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 8a56e45cdc..15b97f694b 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -44,21 +44,12 @@ namespace MediaBrowser.Controller.Entities.TV } /// - /// Override to use the provider Ids so it will be portable + /// Gets the user data key. /// - /// The user data id. - [IgnoreDataMember] - public override Guid UserDataId + /// System.String. + 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 diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index 9150057c82..1e4e6cb06b 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -6,5 +6,13 @@ namespace MediaBrowser.Controller.Entities /// public class Year : BaseItem { + /// + /// Gets the user data key. + /// + /// System.String. + public override string GetUserDataKey() + { + return Name; + } } } diff --git a/MediaBrowser.Controller/Library/DtoBuilder.cs b/MediaBrowser.Controller/Library/DtoBuilder.cs deleted file mode 100644 index 7f9a6f1879..0000000000 --- a/MediaBrowser.Controller/Library/DtoBuilder.cs +++ /dev/null @@ -1,952 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Library -{ - /// - /// Generates DTO's from domain entities - /// - public class DtoBuilder - { - /// - /// The index folder delimeter - /// - const string IndexFolderDelimeter = "-index-"; - - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly IUserManager _userManager; - - public DtoBuilder(ILogger logger, ILibraryManager libraryManager, IUserManager userManager) - { - _logger = logger; - _libraryManager = libraryManager; - _userManager = userManager; - } - - /// - /// Gets the dto base item. - /// - /// The item. - /// The fields. - /// Task{DtoBaseItem}. - /// item - public async Task GetBaseItemDto(BaseItem item, List fields) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - if (fields == null) - { - throw new ArgumentNullException("fields"); - } - - var dto = new BaseItemDto(); - - var tasks = new List(); - - if (fields.Contains(ItemFields.Studios)) - { - dto.Studios = item.Studios; - } - - if (fields.Contains(ItemFields.People)) - { - tasks.Add(AttachPeople(dto, item)); - } - - if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) - { - try - { - AttachPrimaryImageAspectRatio(dto, item); - } - 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, item.Name); - } - } - - AttachBasicFields(dto, item, fields); - - // Make sure all the tasks we kicked off have completed. - if (tasks.Count > 0) - { - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - return dto; - } - - /// - /// Converts a BaseItem to a DTOBaseItem - /// - /// The item. - /// The user. - /// The fields. - /// Task{DtoBaseItem}. - /// item - public async Task GetBaseItemDto(BaseItem item, User user, List fields) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - if (user == null) - { - throw new ArgumentNullException("user"); - } - if (fields == null) - { - throw new ArgumentNullException("fields"); - } - - var dto = new BaseItemDto(); - - var tasks = new List(); - - if (fields.Contains(ItemFields.Studios)) - { - dto.Studios = item.Studios; - } - - if (fields.Contains(ItemFields.People)) - { - tasks.Add(AttachPeople(dto, item)); - } - - tasks.Add(AttachUserSpecificInfo(dto, item, user, fields)); - - if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) - { - try - { - AttachPrimaryImageAspectRatio(dto, item); - } - 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, item.Name); - } - } - - AttachBasicFields(dto, item, fields); - - // Make sure all the tasks we kicked off have completed. - if (tasks.Count > 0) - { - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - return dto; - } - - /// - /// Attaches the user specific info. - /// - /// The dto. - /// The item. - /// The user. - /// The fields. - private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List fields) - { - if (fields.Contains(ItemFields.UserData)) - { - var userData = await _userManager.GetUserData(user.Id, item.UserDataId).ConfigureAwait(false); - - dto.UserData = GetUserItemDataDto(userData); - } - - if (item.IsFolder && fields.Contains(ItemFields.DisplayPreferencesId)) - { - dto.DisplayPreferencesId = ((Folder) item).GetDisplayPreferencesId(user.Id).ToString(); - } - - if (item.IsFolder) - { - if (fields.Contains(ItemFields.ItemCounts)) - { - var folder = (Folder)item; - - // Skip sorting since all we want is a count - dto.ChildCount = folder.GetChildren(user).Count(); - - await SetSpecialCounts(folder, user, dto, _userManager).ConfigureAwait(false); - } - } - } - - /// - /// Attaches the primary image aspect ratio. - /// - /// The dto. - /// The item. - /// Task. - private void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item) - { - var path = item.PrimaryImagePath; - - if (string.IsNullOrEmpty(path)) - { - return; - } - - var metaFileEntry = item.ResolveArgs.GetMetaFileByPath(path); - - // See if we can avoid a file system lookup by looking for the file in ResolveArgs - var dateModified = metaFileEntry == null ? File.GetLastWriteTimeUtc(path) : metaFileEntry.Value.LastWriteTimeUtc; - - ImageSize size; - - try - { - size = Kernel.Instance.ImageManager.GetImageSize(path, dateModified); - } - catch (FileNotFoundException) - { - _logger.Error("Image file does not exist: {0}", path); - return; - } - catch (Exception ex) - { - _logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path); - return; - } - - foreach (var enhancer in Kernel.Instance.ImageEnhancers - .Where(i => i.Supports(item, ImageType.Primary))) - { - - size = enhancer.GetEnhancedImageSize(item, ImageType.Primary, 0, size); - } - - dto.PrimaryImageAspectRatio = size.Width / size.Height; - } - - /// - /// Sets simple property values on a DTOBaseItem - /// - /// The dto. - /// The item. - /// The fields. - private void AttachBasicFields(BaseItemDto dto, BaseItem item, List fields) - { - if (fields.Contains(ItemFields.DateCreated)) - { - dto.DateCreated = item.DateCreated; - } - - if (fields.Contains(ItemFields.DisplayMediaType)) - { - dto.DisplayMediaType = item.DisplayMediaType; - } - - if (fields.Contains(ItemFields.Budget)) - { - dto.Budget = item.Budget; - } - - if (fields.Contains(ItemFields.EndDate)) - { - dto.EndDate = item.EndDate; - } - - if (fields.Contains(ItemFields.HomePageUrl)) - { - dto.HomePageUrl = item.HomePageUrl; - } - - if (fields.Contains(ItemFields.ProductionLocations)) - { - dto.ProductionLocations = item.ProductionLocations; - } - - dto.AspectRatio = item.AspectRatio; - - dto.BackdropImageTags = GetBackdropImageTags(item); - - if (fields.Contains(ItemFields.Genres)) - { - dto.Genres = item.Genres; - } - - if (item.Images != null) - { - dto.ImageTags = new Dictionary(); - - foreach (var image in item.Images) - { - ImageType type; - - if (Enum.TryParse(image.Key, true, out type)) - { - dto.ImageTags[type] = Kernel.Instance.ImageManager.GetImageCacheTag(item, type, image.Value); - } - } - } - - dto.Id = GetClientItemId(item); - dto.IndexNumber = item.IndexNumber; - dto.IsFolder = item.IsFolder; - dto.Language = item.Language; - dto.MediaType = item.MediaType; - dto.LocationType = item.LocationType; - - var localTrailerCount = item.LocalTrailers == null ? 0 : item.LocalTrailers.Count; - - if (localTrailerCount > 0) - { - dto.LocalTrailerCount = localTrailerCount; - } - - dto.Name = item.Name; - dto.OfficialRating = item.OfficialRating; - - var hasOverview = fields.Contains(ItemFields.Overview); - var hasHtmlOverview = fields.Contains(ItemFields.OverviewHtml); - - if (hasOverview || hasHtmlOverview) - { - var strippedOverview = string.IsNullOrEmpty(item.Overview) ? item.Overview : item.Overview.StripHtml(); - - if (hasOverview) - { - dto.Overview = strippedOverview; - } - - // Only supply the html version if there was actually html content - if (hasHtmlOverview) - { - dto.OverviewHtml = item.Overview; - } - } - - // If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance - if (dto.BackdropImageTags.Count == 0) - { - var parentWithBackdrop = GetParentBackdropItem(item); - - if (parentWithBackdrop != null) - { - dto.ParentBackdropItemId = GetClientItemId(parentWithBackdrop); - dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop); - } - } - - if (item.Parent != null && fields.Contains(ItemFields.ParentId)) - { - dto.ParentId = GetClientItemId(item.Parent); - } - - dto.ParentIndexNumber = item.ParentIndexNumber; - - // If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance - if (!dto.HasLogo) - { - var parentWithLogo = GetParentLogoItem(item); - - if (parentWithLogo != null) - { - dto.ParentLogoItemId = GetClientItemId(parentWithLogo); - - dto.ParentLogoImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImage(ImageType.Logo)); - } - } - - if (fields.Contains(ItemFields.Path)) - { - dto.Path = item.Path; - } - - dto.PremiereDate = item.PremiereDate; - dto.ProductionYear = item.ProductionYear; - - if (fields.Contains(ItemFields.ProviderIds)) - { - dto.ProviderIds = item.ProviderIds; - } - - dto.RunTimeTicks = item.RunTimeTicks; - - if (fields.Contains(ItemFields.SortName)) - { - dto.SortName = item.SortName; - } - - if (fields.Contains(ItemFields.Taglines)) - { - dto.Taglines = item.Taglines; - } - - if (fields.Contains(ItemFields.TrailerUrls)) - { - dto.TrailerUrls = item.TrailerUrls; - } - - dto.Type = item.GetType().Name; - dto.CommunityRating = item.CommunityRating; - - if (item.IsFolder) - { - var folder = (Folder)item; - - if (fields.Contains(ItemFields.IndexOptions)) - { - dto.IndexOptions = folder.IndexByOptionStrings.ToArray(); - } - } - - // Add audio info - var audio = item as Audio; - if (audio != null) - { - if (fields.Contains(ItemFields.AudioInfo)) - { - dto.Album = audio.Album; - dto.AlbumArtist = audio.AlbumArtist; - dto.Artist = audio.Artist; - } - } - - // Add video info - var video = item as Video; - if (video != null) - { - dto.VideoType = video.VideoType; - dto.VideoFormat = video.VideoFormat; - dto.IsoType = video.IsoType; - - if (fields.Contains(ItemFields.Chapters) && video.Chapters != null) - { - dto.Chapters = video.Chapters.Select(c => GetChapterInfoDto(c, item)).ToList(); - } - } - - if (fields.Contains(ItemFields.MediaStreams)) - { - // Add VideoInfo - var iHasMediaStreams = item as IHasMediaStreams; - - if (iHasMediaStreams != null) - { - dto.MediaStreams = iHasMediaStreams.MediaStreams; - } - } - - // Add MovieInfo - var movie = item as Movie; - - if (movie != null) - { - var specialFeatureCount = movie.SpecialFeatures == null ? 0 : movie.SpecialFeatures.Count; - - if (specialFeatureCount > 0) - { - dto.SpecialFeatureCount = specialFeatureCount; - } - } - - if (fields.Contains(ItemFields.SeriesInfo)) - { - // Add SeriesInfo - var series = item as Series; - - if (series != null) - { - dto.AirDays = series.AirDays; - dto.AirTime = series.AirTime; - dto.Status = series.Status; - } - - // Add EpisodeInfo - var episode = item as Episode; - - if (episode != null) - { - series = item.FindParent(); - - dto.SeriesId = GetClientItemId(series); - dto.SeriesName = series.Name; - } - - // Add SeasonInfo - var season = item as Season; - - if (season != null) - { - series = item.FindParent(); - - dto.SeriesId = GetClientItemId(series); - dto.SeriesName = series.Name; - } - } - } - - /// - /// Since it can be slow to make all of these calculations independently, this method will provide a way to do them all at once - /// - /// The folder. - /// The user. - /// The dto. - /// The user manager. - /// Task. - private static async Task SetSpecialCounts(Folder folder, User user, BaseItemDto dto, IUserManager userManager) - { - var rcentlyAddedItemCount = 0; - var recursiveItemCount = 0; - - double totalPercentPlayed = 0; - - // 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); - - recursiveItemCount++; - - // Check is recently added - if (child.IsRecentlyAdded(user)) - { - rcentlyAddedItemCount++; - } - - // Incrememt totalPercentPlayed - if (userdata != null) - { - if (userdata.PlayCount > 0) - { - totalPercentPlayed += 100; - } - else if (userdata.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0) - { - double itemPercent = userdata.PlaybackPositionTicks; - itemPercent /= child.RunTimeTicks.Value; - totalPercentPlayed += itemPercent; - } - } - } - - dto.RecursiveItemCount = recursiveItemCount; - dto.RecentlyAddedItemCount = rcentlyAddedItemCount; - - if (recursiveItemCount > 0) - { - dto.PlayedPercentage = totalPercentPlayed / recursiveItemCount; - } - } - - /// - /// Attaches People DTO's to a DTOBaseItem - /// - /// The dto. - /// The item. - /// Task. - private async Task AttachPeople(BaseItemDto dto, BaseItem item) - { - if (item.People == null) - { - return; - } - - // Attach People by transforming them into BaseItemPerson (DTO) - dto.People = new BaseItemPerson[item.People.Count]; - - // Ordering by person type to ensure actors and artists are at the front. - // This is taking advantage of the fact that they both begin with A - // This should be improved in the future - var entities = await Task.WhenAll(item.People.OrderBy(i => i.Type).Select(c => - - Task.Run(async () => - { - try - { - return await _libraryManager.GetPerson(c.Name).ConfigureAwait(false); - } - catch (IOException ex) - { - _logger.ErrorException("Error getting person {0}", ex, c.Name); - return null; - } - }) - - )).ConfigureAwait(false); - - for (var i = 0; i < item.People.Count; i++) - { - var person = item.People[i]; - - var baseItemPerson = new BaseItemPerson - { - Name = person.Name, - Role = person.Role, - Type = person.Type - }; - - var ibnObject = entities[i]; - - if (ibnObject != null) - { - var primaryImagePath = ibnObject.PrimaryImagePath; - - if (!string.IsNullOrEmpty(primaryImagePath)) - { - baseItemPerson.PrimaryImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(ibnObject, ImageType.Primary, primaryImagePath); - } - } - - dto.People[i] = baseItemPerson; - } - } - - /// - /// If an item does not any backdrops, this can be used to find the first parent that does have one - /// - /// The item. - /// BaseItem. - private BaseItem GetParentBackdropItem(BaseItem item) - { - var parent = item.Parent; - - while (parent != null) - { - if (parent.BackdropImagePaths != null && parent.BackdropImagePaths.Count > 0) - { - return parent; - } - - parent = parent.Parent; - } - - return null; - } - - /// - /// If an item does not have a logo, this can be used to find the first parent that does have one - /// - /// The item. - /// BaseItem. - private BaseItem GetParentLogoItem(BaseItem item) - { - var parent = item.Parent; - - while (parent != null) - { - if (parent.HasImage(ImageType.Logo)) - { - return parent; - } - - parent = parent.Parent; - } - - return null; - } - - /// - /// Gets the library update info. - /// - /// The instance containing the event data. - /// LibraryUpdateInfo. - public static LibraryUpdateInfo GetLibraryUpdateInfo(ChildrenChangedEventArgs changeEvent) - { - return new LibraryUpdateInfo - { - Folder = GetBaseItemInfo(changeEvent.Folder), - ItemsAdded = changeEvent.ItemsAdded.Select(GetBaseItemInfo), - ItemsRemoved = changeEvent.ItemsRemoved.Select(i => i.Id), - ItemsUpdated = changeEvent.ItemsUpdated.Select(i => i.Id) - }; - } - - /// - /// Converts a UserItemData to a DTOUserItemData - /// - /// The data. - /// DtoUserItemData. - /// - public UserItemDataDto GetUserItemDataDto(UserItemData data) - { - if (data == null) - { - throw new ArgumentNullException(); - } - - return new UserItemDataDto - { - IsFavorite = data.IsFavorite, - Likes = data.Likes, - PlaybackPositionTicks = data.PlaybackPositionTicks, - PlayCount = data.PlayCount, - Rating = data.Rating, - Played = data.Played, - LastPlayedDate = data.LastPlayedDate - }; - } - - /// - /// Gets the chapter info dto. - /// - /// The chapter info. - /// The item. - /// ChapterInfoDto. - private ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item) - { - var dto = new ChapterInfoDto - { - Name = chapterInfo.Name, - StartPositionTicks = chapterInfo.StartPositionTicks - }; - - if (!string.IsNullOrEmpty(chapterInfo.ImagePath)) - { - dto.ImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Chapter, chapterInfo.ImagePath); - } - - return dto; - } - - /// - /// Converts a BaseItem to a BaseItemInfo - /// - /// The item. - /// BaseItemInfo. - /// item - public static BaseItemInfo GetBaseItemInfo(BaseItem item) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - var info = new BaseItemInfo - { - Id = GetClientItemId(item), - Name = item.Name, - Type = item.GetType().Name, - IsFolder = item.IsFolder, - RunTimeTicks = item.RunTimeTicks - }; - - var imagePath = item.PrimaryImagePath; - - if (!string.IsNullOrEmpty(imagePath)) - { - info.PrimaryImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Primary, imagePath); - } - - if (item.BackdropImagePaths != null && item.BackdropImagePaths.Count > 0) - { - imagePath = item.BackdropImagePaths[0]; - - if (!string.IsNullOrEmpty(imagePath)) - { - info.BackdropImageTag = Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Backdrop, imagePath); - } - } - - return info; - } - - /// - /// Gets client-side Id of a server-side BaseItem - /// - /// The item. - /// System.String. - /// item - public static string GetClientItemId(BaseItem item) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - var indexFolder = item as IndexFolder; - - if (indexFolder != null) - { - return GetClientItemId(indexFolder.Parent) + IndexFolderDelimeter + (indexFolder.IndexName ?? string.Empty) + IndexFolderDelimeter + indexFolder.Id; - } - - return item.Id.ToString(); - } - - /// - /// Converts a User to a DTOUser - /// - /// The user. - /// DtoUser. - /// user - 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; - } - - /// - /// Gets a BaseItem based upon it's client-side item id - /// - /// The id. - /// The user manager. - /// The library manager. - /// The user id. - /// BaseItem. - public static BaseItem GetItemByClientId(string id, IUserManager userManager, ILibraryManager libraryManager, Guid? userId = null) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - // If the item is an indexed folder we have to do a special routine to get it - var isIndexFolder = id.IndexOf(IndexFolderDelimeter, StringComparison.OrdinalIgnoreCase) != -1; - - if (isIndexFolder) - { - if (userId.HasValue) - { - return GetIndexFolder(id, userId.Value, userManager, libraryManager); - } - } - - BaseItem item = null; - - if (userId.HasValue || !isIndexFolder) - { - item = libraryManager.GetItemById(new Guid(id)); - } - - // If we still don't find it, look within individual user views - if (item == null && !userId.HasValue && isIndexFolder) - { - foreach (var user in userManager.Users) - { - item = GetItemByClientId(id, userManager, libraryManager, user.Id); - - if (item != null) - { - break; - } - } - } - - return item; - } - - /// - /// Finds an index folder based on an Id and userId - /// - /// The id. - /// The user id. - /// The user manager. - /// The library manager. - /// BaseItem. - private static BaseItem GetIndexFolder(string id, Guid userId, IUserManager userManager, ILibraryManager libraryManager) - { - var user = userManager.GetUserById(userId); - - var stringSeparators = new[] { IndexFolderDelimeter }; - - // Split using the delimeter - var values = id.Split(stringSeparators, StringSplitOptions.None).ToList(); - - // Get the top folder normally using the first id - var folder = GetItemByClientId(values[0], userManager, libraryManager, userId) as Folder; - - values.RemoveAt(0); - - // Get indexed folders using the remaining values in the id string - return GetIndexFolder(values, folder, user); - } - - /// - /// Gets indexed folders based on a list of index names and folder id's - /// - /// The values. - /// The parent folder. - /// The user. - /// BaseItem. - private static BaseItem GetIndexFolder(List values, Folder parentFolder, User user) - { - // The index name is first - var indexBy = values[0]; - - // The index folder id is next - var indexFolderId = new Guid(values[1]); - - // Remove them from the lst - values.RemoveRange(0, 2); - - // Get the IndexFolder - var indexFolder = parentFolder.GetChildren(user, indexBy).FirstOrDefault(i => i.Id == indexFolderId) as Folder; - - // Nested index folder - if (values.Count > 0) - { - return GetIndexFolder(values, indexFolder, user); - } - - return indexFolder; - } - - /// - /// Gets the backdrop image tags. - /// - /// The item. - /// List{System.String}. - private List GetBackdropImageTags(BaseItem item) - { - if (item.BackdropImagePaths == null) - { - return new List(); - } - - return item.BackdropImagePaths.Select(p => Kernel.Instance.ImageManager.GetImageCacheTag(item, ImageType.Backdrop, p)).ToList(); - } - } -} diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index ad46cf7c33..0fad1d05dc 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -173,24 +173,5 @@ namespace MediaBrowser.Controller.Library /// The new password. /// Task. Task ChangePassword(User user, string newPassword); - - /// - /// Saves the user data. - /// - /// The user id. - /// The user data id. - /// The user data. - /// The cancellation token. - /// Task. - Task SaveUserData(Guid userId, Guid userDataId, UserItemData userData, - CancellationToken cancellationToken); - - /// - /// Gets the user data. - /// - /// The user id. - /// The user data id. - /// Task{UserItemData}. - Task GetUserData(Guid userId, Guid userDataId); } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 558a9d8e36..71245a3bb4 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -72,6 +72,7 @@ + @@ -107,7 +108,7 @@ - + diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index 592206faf9..1b4efc58b1 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/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. /// /// The user id. - /// The user data id. + /// The key. /// The user data. /// The cancellation token. /// Task. - Task SaveUserData(Guid userId, Guid userDataId, UserItemData userData, + Task SaveUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken); /// /// Gets the user data. /// /// The user id. - /// The user data id. + /// The key. /// Task{UserItemData}. - Task GetUserData(Guid userId, Guid userDataId); + Task GetUserData(Guid userId, string key); } } diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs index aaf3fe6bfb..3f71e398a3 100644 --- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs @@ -29,19 +29,7 @@ namespace MediaBrowser.Controller.Providers /// /// The _id /// - protected Guid _id; - /// - /// Gets the id. - /// - /// The id. - public virtual Guid Id - { - get - { - if (_id == Guid.Empty) _id = GetType().FullName.GetMD5(); - return _id; - } - } + protected readonly Guid Id; /// /// 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); } - + /// /// 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); } /// @@ -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 /// true if [has file system stamp changed] [the specified item]; otherwise, false. protected bool HasFileSystemStampChanged(BaseItem item, BaseProviderInfo providerInfo) { - return GetCurrentFileSystemStamp(item) != providerInfo.FileSystemStamp; + return !string.Equals(GetCurrentFileSystemStamp(item), providerInfo.FileSystemStamp); } /// @@ -279,7 +279,7 @@ namespace MediaBrowser.Controller.Providers /// /// The item. /// Guid. - private Guid GetCurrentFileSystemStamp(BaseItem item) + private string GetCurrentFileSystemStamp(BaseItem item) { if (UseParentFileSystemStamp(item) && item.Parent != null) { diff --git a/MediaBrowser.Controller/Providers/BaseProviderInfo.cs b/MediaBrowser.Controller/Providers/BaseProviderInfo.cs index 877ba5c4bf..5a72491c1e 100644 --- a/MediaBrowser.Controller/Providers/BaseProviderInfo.cs +++ b/MediaBrowser.Controller/Providers/BaseProviderInfo.cs @@ -7,11 +7,6 @@ namespace MediaBrowser.Controller.Providers /// public class BaseProviderInfo { - /// - /// Gets or sets the provider id. - /// - /// The provider id. - public Guid ProviderId { get; set; } /// /// Gets or sets the last refreshed. /// @@ -21,7 +16,7 @@ namespace MediaBrowser.Controller.Providers /// Gets or sets the file system stamp. /// /// The file system stamp. - public Guid FileSystemStamp { get; set; } + public string FileSystemStamp { get; set; } /// /// Gets or sets the last refresh status. /// @@ -32,11 +27,6 @@ namespace MediaBrowser.Controller.Providers /// /// The provider version. public string ProviderVersion { get; set; } - /// - /// Gets or sets the data hash. - /// - /// The data hash. - public Guid DataHash { get; set; } } /// diff --git a/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs index 3152ceac93..94fe386806 100644 --- a/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs +++ b/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)); diff --git a/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs index fb8370248c..8e20f0fada 100644 --- a/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs +++ b/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) { } - + } /// /// Class MovieDbProvider @@ -33,7 +34,7 @@ namespace MediaBrowser.Controller.Providers.Movies public class MovieDbProvider : BaseMetadataProvider, IDisposable { protected readonly IProviderManager ProviderManager; - + /// /// The movie db /// @@ -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; } diff --git a/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs index b533848412..72169f245e 100644 --- a/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs +++ b/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; } + + /// + /// Helper method for Dictionaries since they throw on not-found keys + /// + /// + /// + /// The dictionary. + /// The key. + /// The default value. + /// ``1. + private static U GetValueOrDefault(Dictionary dictionary, T key, U defaultValue) + { + U val; + if (!dictionary.TryGetValue(key, out val)) + { + val = defaultValue; + } + return val; + + } } } diff --git a/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs index ec7a96d99d..1dd5c7cd25 100644 --- a/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs +++ b/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(); diff --git a/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs b/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs index f58bd3bb77..f7e0eef48d 100644 --- a/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs +++ b/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 diff --git a/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs index 4d06143fce..a7fc4586f1 100644 --- a/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs +++ b/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)); diff --git a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs index 76d84a653d..d464a64a07 100644 --- a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs +++ b/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 /// /// The user manager. IUserManager UserManager { get; set; } + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + IUserDataRepository UserDataRepository { get; set; } } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index eb2b4ddebe..0c18564ec8 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -153,27 +153,6 @@ namespace MediaBrowser.Model.Configuration [ProtoMember(52)] public bool DownloadHDFanArt { get; set; } - /// - /// Gets or sets the name of the item repository that should be used - /// - /// The item repository. - [ProtoMember(24)] - public string ItemRepository { get; set; } - - /// - /// Gets or sets the name of the user repository that should be used - /// - /// The user repository. - [ProtoMember(25)] - public string UserRepository { get; set; } - - /// - /// Gets or sets the name of the user data repository that should be used - /// - /// The user data repository. - [ProtoMember(26)] - public string UserDataRepository { get; set; } - /// /// Characters to be replaced with a ' ' in strings to create a sort name /// @@ -202,13 +181,6 @@ namespace MediaBrowser.Model.Configuration [ProtoMember(30)] public bool ShowLogWindow { get; set; } - /// - /// Gets or sets the name of the user data repository that should be used - /// - /// The display preferences repository. - [ProtoMember(31)] - public string DisplayPreferencesRepository { get; set; } - /// /// The list of types that will NOT be allowed to have internet providers run against them even if they are turned on. /// diff --git a/MediaBrowser.Model/Connectivity/ClientConnectionInfo.cs b/MediaBrowser.Model/Connectivity/ClientConnectionInfo.cs index 565ce6ec24..a5e44f22ec 100644 --- a/MediaBrowser.Model/Connectivity/ClientConnectionInfo.cs +++ b/MediaBrowser.Model/Connectivity/ClientConnectionInfo.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Connectivity /// /// The user id. [ProtoMember(1)] - public Guid UserId { get; set; } + public string UserId { get; set; } /// /// Gets or sets the type of the client. diff --git a/MediaBrowser.Model/DTO/UserDto.cs b/MediaBrowser.Model/DTO/UserDto.cs index 979422dbe8..b2afa90c0f 100644 --- a/MediaBrowser.Model/DTO/UserDto.cs +++ b/MediaBrowser.Model/DTO/UserDto.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Dto /// /// The id. [ProtoMember(2)] - public Guid Id { get; set; } + public string Id { get; set; } /// /// Gets or sets the primary image tag. diff --git a/MediaBrowser.Model/Plugins/PluginInfo.cs b/MediaBrowser.Model/Plugins/PluginInfo.cs index 2df635db02..fd4cfa78c2 100644 --- a/MediaBrowser.Model/Plugins/PluginInfo.cs +++ b/MediaBrowser.Model/Plugins/PluginInfo.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Model.Plugins /// /// The unique id. [ProtoMember(9)] - public Guid Id { get; set; } + public string Id { get; set; } /// /// Whether or not this plug-in should be automatically updated when a diff --git a/MediaBrowser.Model/Querying/ItemQuery.cs b/MediaBrowser.Model/Querying/ItemQuery.cs index 3a1d14065e..86886d751a 100644 --- a/MediaBrowser.Model/Querying/ItemQuery.cs +++ b/MediaBrowser.Model/Querying/ItemQuery.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Model.Querying /// The user to localize search results for /// /// The user id. - public Guid UserId { get; set; } + public string UserId { get; set; } /// /// Specify this to localize the search to a specific item or folder. Omit to use the root. diff --git a/MediaBrowser.Model/Querying/ItemsByNameQuery.cs b/MediaBrowser.Model/Querying/ItemsByNameQuery.cs index 0bebb085e1..eae2dc0426 100644 --- a/MediaBrowser.Model/Querying/ItemsByNameQuery.cs +++ b/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. /// /// The user id. - public Guid UserId { get; set; } + public string UserId { get; set; } /// /// Gets or sets the start index. /// diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 20716ecc54..afe0740882 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -71,6 +71,6 @@ namespace MediaBrowser.Model.System /// /// The id. [ProtoMember(9)] - public Guid Id { get; set; } + public string Id { get; set; } } } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index d397b15480..3bb5472dfc 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -101,6 +101,8 @@ namespace MediaBrowser.Server.Implementations.Library /// private readonly IUserManager _userManager; + private readonly IUserDataRepository _userDataRepository; + /// /// Gets or sets the configuration manager. /// @@ -136,12 +138,14 @@ namespace MediaBrowser.Server.Implementations.Library /// The task manager. /// The user manager. /// The configuration manager. - public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager) + /// The user data repository. + 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(); ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -903,6 +907,7 @@ namespace MediaBrowser.Server.Implementations.Library userComparer.User = user; userComparer.UserManager = _userManager; + userComparer.UserDataRepository = _userDataRepository; return userComparer; } diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 311a31264f..dbb2d7b320 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/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 /// private readonly ILogger _logger; + private readonly IUserDataRepository _userDataRepository; + /// /// Gets or sets the configuration manager. /// /// The configuration manager. private IServerConfigurationManager ConfigurationManager { get; set; } - private readonly ConcurrentDictionary> _userData = new ConcurrentDictionary>(); - - /// - /// Gets the active user data repository - /// - /// The user data repository. - public IUserDataRepository UserDataRepository { get; set; } - /// /// Gets the active user repository /// @@ -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; } } - - /// - /// Saves display preferences for an item - /// - /// The user id. - /// The user data id. - /// The user data. - /// The cancellation token. - /// Task. - 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; - } - } - - /// - /// Gets the user data. - /// - /// The user id. - /// The user data id. - /// Task{UserItemData}. - public Task GetUserData(Guid userId, Guid userDataId) - { - var key = userId + userDataId.ToString(); - - return _userData.GetOrAdd(key, keyName => RetrieveUserData(userId, userDataId)); - } - - /// - /// Retrieves the user data. - /// - /// The user id. - /// The user data id. - /// Task{UserItemData}. - private async Task RetrieveUserData(Guid userId, Guid userDataId) - { - var userdata = await UserDataRepository.GetUserData(userId, userDataId).ConfigureAwait(false); - - return userdata ?? new UserItemData(); - } } } diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs index 8b135db5a7..ff7222e7c1 100644 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -88,12 +88,6 @@ namespace MediaBrowser.Server.Implementations.Providers Task.Run(() => ValidateCurrentlyRunningProviders()); } - /// - /// Gets or sets the supported providers key. - /// - /// The supported providers key. - private Guid SupportedProvidersKey { get; set; } - /// /// Adds the metadata providers. /// @@ -103,6 +97,11 @@ namespace MediaBrowser.Server.Implementations.Providers MetadataProviders = providers.OrderBy(e => e.Priority).ToArray(); } + /// + /// The _supported providers key + /// + private readonly Guid _supportedProvidersKey = "SupportedProviders".GetMD5(); + /// /// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence /// @@ -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; diff --git a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs index 905d5413a7..c634c760e4 100644 --- a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/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 /// The user manager. public IUserManager UserManager { get; set; } + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataRepository UserDataRepository { get; set; } + /// /// Compares the specified x. /// @@ -41,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting /// DateTime. 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) { diff --git a/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs index 82e76e78d4..a7cbd21499 100644 --- a/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/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 /// DateTime. 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; } } + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataRepository UserDataRepository { get; set; } + /// /// Gets or sets the user manager. /// diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs index 5016358007..fbb0e4f8c5 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite /// /// Class SQLiteDisplayPreferencesRepository /// - class SQLiteDisplayPreferencesRepository : SqliteRepository, IDisplayPreferencesRepository + public class SQLiteDisplayPreferencesRepository : SqliteRepository, IDisplayPreferencesRepository { /// /// The repository name diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs index 467628a78f..4df81aacca 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs +++ b/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 /// public class SQLiteUserDataRepository : SqliteRepository, IUserDataRepository { + private readonly ConcurrentDictionary> _userData = new ConcurrentDictionary>(); + /// /// The repository name /// @@ -45,9 +48,6 @@ namespace MediaBrowser.Server.Implementations.Sqlite } } - /// - /// The _protobuf serializer - /// private readonly IJsonSerializer _jsonSerializer; /// @@ -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. /// /// The user id. - /// The user data id. + /// The key. /// The user data. /// The cancellation token. /// Task. - /// - /// userData + /// userData /// or /// cancellationToken /// or /// userId /// or - /// userDataId - /// - public async Task SaveUserData(Guid userId, Guid userDataId, UserItemData userData, CancellationToken cancellationToken) + /// userDataId + 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; + } + } + + /// + /// Persists the user data. + /// + /// The user id. + /// The key. + /// The user data. + /// The cancellation token. + /// Task. + 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. /// /// The user id. - /// The user data id. + /// The key. /// Task{UserItemData}. /// /// userId /// or - /// userDataId + /// key /// - public async Task GetUserData(Guid userId, Guid userDataId) + public Task 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)); + } + /// + /// Retrieves the user data. + /// + /// The user id. + /// The key. + /// Task{UserItemData}. + private async Task 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(); } } } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index afc68fe67b..59395e4698 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/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 /// The media encoder. private IMediaEncoder MediaEncoder { get; set; } + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + private IUserDataRepository UserDataRepository { get; set; } + /// /// The full path to our startmenu shortcut /// @@ -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 /// Task. private async Task ConfigureDisplayPreferencesRepositories() { - var repositories = GetExports(); - - 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 /// Task. private async Task ConfigureItemRepositories() { - var repositories = GetExports(); - - 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. /// /// Task. - private async Task ConfigureUserDataRepositories() + private Task ConfigureUserDataRepositories() { - var repositories = GetExports(); - - 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(); - - 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(); /// /// Gets the system status. diff --git a/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs b/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs index 98d7fb4775..19c42e8d8d 100644 --- a/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs +++ b/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 /// The e. void userManager_UserUpdated(object sender, GenericEventArgs e) { - var dto = new DtoBuilder(_logger, _libraryManager, _userManager).GetUserDto(e.Argument); + var dto = new UserDtoBuilder(_logger).GetUserDto(e.Argument); _serverManager.SendWebSocketMessage("UserUpdated", dto); } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 7756fb5b9a..c773503b01 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/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 { -- cgit v1.2.3