using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Api.UserLibrary { /// /// Class GetItem /// [Route("/Users/{UserId}/Items/{Id}", "GET")] [Api(Description = "Gets an item from a user's library")] public class GetItem : IReturn { /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public Guid UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } /// /// Class GetItem /// [Route("/Users/{UserId}/Items/Root", "GET")] [Api(Description = "Gets the root folder from a user's library")] public class GetRootFolder : IReturn { /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public Guid UserId { get; set; } } /// /// Class GetIntros /// [Route("/Users/{UserId}/Items/{Id}/Intros", "GET")] [Api(("Gets intros to play before the main media item plays"))] public class GetIntros : IReturn> { /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public Guid UserId { get; set; } /// /// Gets or sets the item id. /// /// The item id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } /// /// Class MarkFavoriteItem /// [Route("/Users/{UserId}/FavoriteItems/{Id}", "POST")] [Api(Description = "Marks an item as a favorite")] public class MarkFavoriteItem : IReturn { /// /// 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 id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } } /// /// Class UnmarkFavoriteItem /// [Route("/Users/{UserId}/FavoriteItems/{Id}", "DELETE")] [Api(Description = "Unmarks an item as a favorite")] public class UnmarkFavoriteItem : IReturn { /// /// 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 id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } } /// /// Class ClearUserItemRating /// [Route("/Users/{UserId}/Items/{Id}/Rating", "DELETE")] [Api(Description = "Deletes a user's saved personal rating for an item")] public class DeleteUserItemRating : IReturn { /// /// 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 id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } } /// /// Class UpdateUserItemRating /// [Route("/Users/{UserId}/Items/{Id}/Rating", "POST")] [Api(Description = "Updates a user's rating for an item")] public class UpdateUserItemRating : IReturn { /// /// 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 id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } /// /// Gets or sets a value indicating whether this is likes. /// /// true if likes; otherwise, false. [ApiMember(Name = "Likes", Description = "Whether the user likes the item or not. true/false", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")] public bool Likes { get; set; } } /// /// Class MarkPlayedItem /// [Route("/Users/{UserId}/PlayedItems/{Id}", "POST")] [Api(Description = "Marks an item as played")] public class MarkPlayedItem : IReturn { /// /// 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 id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } } /// /// Class MarkUnplayedItem /// [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE")] [Api(Description = "Marks an item as unplayed")] public class MarkUnplayedItem : IReturn { /// /// 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 id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } } /// /// Class OnPlaybackStart /// [Route("/Users/{UserId}/PlayingItems/{Id}", "POST")] [Api(Description = "Reports that a user has begun playing an item")] public class OnPlaybackStart : 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 id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } } /// /// Class OnPlaybackProgress /// [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST")] [Api(Description = "Reports a user's playback progress")] public class OnPlaybackProgress : 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 id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } /// /// Gets or sets the position ticks. /// /// The position ticks. [ApiMember(Name = "PositionTicks", Description = "Optional. The current position, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] public long? PositionTicks { get; set; } [ApiMember(Name = "IsPaused", Description = "Indicates if the player is paused.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] public bool IsPaused { get; set; } [ApiMember(Name = "IsMuted", Description = "Indicates if the player is muted.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] public bool IsMuted { get; set; } } /// /// Class OnPlaybackStopped /// [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE")] [Api(Description = "Reports that a user has stopped playing an item")] public class OnPlaybackStopped : 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 id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } /// /// Gets or sets the position ticks. /// /// The position ticks. [ApiMember(Name = "PositionTicks", Description = "Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "DELETE")] public long? PositionTicks { get; set; } } /// /// Class GetLocalTrailers /// [Route("/Users/{UserId}/Items/{Id}/LocalTrailers", "GET")] [Api(Description = "Gets local trailers for an item")] public class GetLocalTrailers : IReturn> { /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public Guid UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } /// /// Class GetSpecialFeatures /// [Route("/Users/{UserId}/Items/{Id}/SpecialFeatures", "GET")] [Api(Description = "Gets special features for an item")] public class GetSpecialFeatures : IReturn> { /// /// Gets or sets the user id. /// /// The user id. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public Guid UserId { get; set; } /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Movie Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } /// /// Class UserLibraryService /// public class UserLibraryService : BaseApiService { /// /// The _user manager /// private readonly IUserManager _userManager; /// /// The _user data repository /// private readonly IUserDataRepository _userDataRepository; /// /// The _library manager /// private readonly ILibraryManager _libraryManager; private readonly IItemRepository _itemRepo; private readonly ISessionManager _sessionManager; /// /// Initializes a new instance of the class. /// /// The user manager. /// The library manager. /// The user data repository. /// The item repo. /// jsonSerializer public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo, ISessionManager sessionManager) { _userManager = userManager; _libraryManager = libraryManager; _userDataRepository = userDataRepository; _itemRepo = itemRepo; _sessionManager = sessionManager; } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetSpecialFeatures request) { var result = GetAsync(request).Result; return ToOptimizedResult(result); } private Task GetAsync(GetSpecialFeatures request) { var user = _userManager.GetUserById(request.UserId); var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); // Get everything var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList(); var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository, _itemRepo); var movie = item as Movie; // Get them from the db if (movie != null) { // Avoid implicitly captured closure var movie1 = movie; var tasks = movie.SpecialFeatureIds .Select(_itemRepo.RetrieveItem) .OrderBy(i => i.SortName) .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user, movie1)); return Task.WhenAll(tasks); } var series = item as Series; // Get them from the child tree if (series != null) { var tasks = series .RecursiveChildren .OfType() .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0) .OrderBy(i => { if (i.PremiereDate.HasValue) { return i.PremiereDate.Value; } if (i.ProductionYear.HasValue) { return new DateTime(i.ProductionYear.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc); } return DateTime.MinValue; }) .ThenBy(i => i.SortName) .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user)); return Task.WhenAll(tasks); } throw new ArgumentException("The item does not support special features"); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetLocalTrailers request) { var result = GetAsync(request).Result; return ToOptimizedResult(result); } private Task GetAsync(GetLocalTrailers request) { var user = _userManager.GetUserById(request.UserId); var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); // Get everything var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList(); var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository, _itemRepo); var tasks = item.LocalTrailerIds .Select(_itemRepo.RetrieveItem) .OrderBy(i => i.SortName) .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user, item)); return Task.WhenAll(tasks); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetItem request) { var user = _userManager.GetUserById(request.UserId); var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); // Get everything var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList(); var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository, _itemRepo); var result = dtoBuilder.GetBaseItemDto(item, fields, user).Result; return ToOptimizedResult(result); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetRootFolder request) { var user = _userManager.GetUserById(request.UserId); var item = user.RootFolder; // Get everything var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList(); var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository, _itemRepo); var result = dtoBuilder.GetBaseItemDto(item, fields, user).Result; return ToOptimizedResult(result); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetIntros request) { var user = _userManager.GetUserById(request.UserId); var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); var result = _libraryManager.GetIntros(item, user); return ToOptimizedResult(result); } /// /// Posts the specified request. /// /// The request. public object Post(MarkFavoriteItem request) { var dto = MarkFavorite(request.UserId, request.Id, true).Result; return ToOptimizedResult(dto); } /// /// Deletes the specified request. /// /// The request. public object Delete(UnmarkFavoriteItem request) { var dto = MarkFavorite(request.UserId, request.Id, false).Result; return ToOptimizedResult(dto); } private async Task MarkFavorite(Guid userId, string itemId, bool isFavorite) { var user = _userManager.GetUserById(userId); var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : DtoBuilder.GetItemByClientId(itemId, _userManager, _libraryManager, user.Id); var key = item.GetUserDataKey(); // Get the user data for this item var data = _userDataRepository.GetUserData(user.Id, key); // Set favorite status data.IsFavorite = isFavorite; await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false); data = _userDataRepository.GetUserData(user.Id, key); return DtoBuilder.GetUserItemDataDto(data); } /// /// Deletes the specified request. /// /// The request. public object Delete(DeleteUserItemRating request) { var dto = UpdateUserItemRating(request.UserId, request.Id, null).Result; return ToOptimizedResult(dto); } /// /// Posts the specified request. /// /// The request. public object Post(UpdateUserItemRating request) { var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes).Result; return ToOptimizedResult(dto); } private async Task UpdateUserItemRating(Guid userId, string itemId, bool? likes) { var user = _userManager.GetUserById(userId); var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : DtoBuilder.GetItemByClientId(itemId, _userManager, _libraryManager, user.Id); var key = item.GetUserDataKey(); // Get the user data for this item var data = _userDataRepository.GetUserData(user.Id, key); data.Likes = likes; await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false); data = _userDataRepository.GetUserData(user.Id, key); return DtoBuilder.GetUserItemDataDto(data); } /// /// Posts the specified request. /// /// The request. public object Post(MarkPlayedItem request) { var user = _userManager.GetUserById(request.UserId); var task = UpdatePlayedStatus(user, request.Id, true); return ToOptimizedResult(task.Result); } private SessionInfo GetSession() { var auth = RequestFilterAttribute.GetAuthorization(RequestContext); string deviceId; string client; string version; auth.TryGetValue("DeviceId", out deviceId); auth.TryGetValue("Client", out client); auth.TryGetValue("Version", out version); return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, deviceId) && string.Equals(i.Client, client) && string.Equals(i.ApplicationVersion, version)); } /// /// Posts the specified request. /// /// The request. public void Post(OnPlaybackStart request) { var user = _userManager.GetUserById(request.UserId); var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); _sessionManager.OnPlaybackStart(item, GetSession().Id); } /// /// Posts the specified request. /// /// The request. public void Post(OnPlaybackProgress request) { var user = _userManager.GetUserById(request.UserId); var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); var task = _sessionManager.OnPlaybackProgress(item, request.PositionTicks, request.IsPaused, request.IsMuted, GetSession().Id); Task.WaitAll(task); } /// /// Posts the specified request. /// /// The request. public void Delete(OnPlaybackStopped request) { var user = _userManager.GetUserById(request.UserId); var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id); var task = _sessionManager.OnPlaybackStopped(item, request.PositionTicks, GetSession().Id); Task.WaitAll(task); } /// /// Deletes the specified request. /// /// The request. public object Delete(MarkUnplayedItem request) { var user = _userManager.GetUserById(request.UserId); var task = UpdatePlayedStatus(user, request.Id, false); return ToOptimizedResult(task.Result); } /// /// Updates the played status. /// /// The user. /// The item id. /// if set to true [was played]. /// Task. private async Task UpdatePlayedStatus(User user, string itemId, bool wasPlayed) { var item = DtoBuilder.GetItemByClientId(itemId, _userManager, _libraryManager, user.Id); await item.SetPlayedStatus(user, wasPlayed, _userDataRepository).ConfigureAwait(false); return DtoBuilder.GetUserItemDataDto(_userDataRepository.GetUserData(user.Id, item.GetUserDataKey())); } } }