From c1c4c85fc250ce76d91a2afbda06c13173a06ec6 Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Sun, 9 Sep 2012 14:32:51 -0400 Subject: [PATCH] Added some favorites api calls --- MediaBrowser.Api/ApiService.cs | 22 +++++- .../HttpHandlers/FavoriteStatusHandler.cs | 44 +++++++++++ MediaBrowser.Api/HttpHandlers/ItemHandler.cs | 13 +--- .../HttpHandlers/ItemListHandler.cs | 6 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + .../DataSerializer.cs | 2 - .../ApiClient.cs | 65 ++++++++++++++-- .../BaseHttpApiClient.cs | 77 +++++++++++++++++-- .../Resolvers/VideoResolver.cs | 6 +- MediaBrowser.Model/DTO/DTOBaseItem.cs | 2 +- MediaBrowser.Model/DTO/DTOUserItemData.cs | 23 ++++++ MediaBrowser.Model/DTO/VideoOutputFormats.cs | 2 +- MediaBrowser.Model/Entities/Folder.cs | 18 +++++ MediaBrowser.Model/Entities/UserItemData.cs | 66 +++++++++++++--- MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + 15 files changed, 308 insertions(+), 40 deletions(-) create mode 100644 MediaBrowser.Api/HttpHandlers/FavoriteStatusHandler.cs create mode 100644 MediaBrowser.Model/DTO/DTOUserItemData.cs diff --git a/MediaBrowser.Api/ApiService.cs b/MediaBrowser.Api/ApiService.cs index 895ca3d198..b3c6237399 100644 --- a/MediaBrowser.Api/ApiService.cs +++ b/MediaBrowser.Api/ApiService.cs @@ -171,7 +171,7 @@ namespace MediaBrowser.Api dto.Type = item.GetType().Name; dto.UserRating = item.UserRating; - dto.UserData = item.GetUserData(user); + dto.UserData = GetDTOUserItemData(item.GetUserData(user)); Folder folder = item as Folder; @@ -394,6 +394,26 @@ namespace MediaBrowser.Api }; } + /// + /// Converts a UserItemData to a DTOUserItemData + /// + public static DTOUserItemData GetDTOUserItemData(UserItemData data) + { + if (data == null) + { + return null; + } + + return new DTOUserItemData() + { + IsFavorite = data.IsFavorite, + Likes = data.Likes, + PlaybackPositionTicks = data.PlaybackPositionTicks, + PlayCount = data.PlayCount, + Rating = data.Rating + }; + } + public static bool IsApiUrlMatch(string url, HttpListenerRequest request) { url = "/api/" + url; diff --git a/MediaBrowser.Api/HttpHandlers/FavoriteStatusHandler.cs b/MediaBrowser.Api/HttpHandlers/FavoriteStatusHandler.cs new file mode 100644 index 0000000000..851120230a --- /dev/null +++ b/MediaBrowser.Api/HttpHandlers/FavoriteStatusHandler.cs @@ -0,0 +1,44 @@ +using MediaBrowser.Common.Net.Handlers; +using MediaBrowser.Model.DTO; +using MediaBrowser.Model.Entities; +using System.ComponentModel.Composition; +using System.Net; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.HttpHandlers +{ + /// + /// Provides a handler to set user favorite status for an item + /// + [Export(typeof(BaseHandler))] + public class FavoriteStatus : BaseSerializationHandler + { + public override bool HandlesRequest(HttpListenerRequest request) + { + return ApiService.IsApiUrlMatch("FavoriteStatus", request); + } + + protected override Task GetObjectToSerialize() + { + // Get the item + BaseItem item = ApiService.GetItemById(QueryString["id"]); + + // Get the user + User user = ApiService.GetUserById(QueryString["userid"], true); + + // Get the user data for this item + UserItemData data = item.GetUserData(user); + + if (data == null) + { + data = new UserItemData(); + item.AddUserData(user, data); + } + + // Set favorite status + data.IsFavorite = QueryString["isfavorite"] == "1"; + + return Task.FromResult(ApiService.GetDTOUserItemData(data)); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Api/HttpHandlers/ItemHandler.cs b/MediaBrowser.Api/HttpHandlers/ItemHandler.cs index 483feb2fba..024647d8a5 100644 --- a/MediaBrowser.Api/HttpHandlers/ItemHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/ItemHandler.cs @@ -7,6 +7,9 @@ using System.Threading.Tasks; namespace MediaBrowser.Api.HttpHandlers { + /// + /// Provides a handler to retrieve a single item + /// [Export(typeof(BaseHandler))] public class ItemHandler : BaseSerializationHandler { @@ -19,7 +22,7 @@ namespace MediaBrowser.Api.HttpHandlers { User user = ApiService.GetUserById(QueryString["userid"], true); - BaseItem item = ItemToSerialize; + BaseItem item = ApiService.GetItemById(QueryString["id"]); if (item == null) { @@ -28,13 +31,5 @@ namespace MediaBrowser.Api.HttpHandlers return ApiService.GetDTOBaseItem(item, user); } - - protected virtual BaseItem ItemToSerialize - { - get - { - return ApiService.GetItemById(QueryString["id"]); - } - } } } diff --git a/MediaBrowser.Api/HttpHandlers/ItemListHandler.cs b/MediaBrowser.Api/HttpHandlers/ItemListHandler.cs index f1d5d0c37f..9845a5b21e 100644 --- a/MediaBrowser.Api/HttpHandlers/ItemListHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/ItemListHandler.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Api.HttpHandlers { return ApiService.IsApiUrlMatch("itemlist", request); } - + protected override Task GetObjectToSerialize() { User user = ApiService.GetUserById(QueryString["userid"], true); @@ -60,6 +60,10 @@ namespace MediaBrowser.Api.HttpHandlers { return parent.GetItemsWithPerson(QueryString["name"], null, user); } + else if (ListType.Equals("favorites", StringComparison.OrdinalIgnoreCase)) + { + return parent.GetFavoriteItems(user); + } throw new InvalidOperationException(); } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 4016a88a34..31bb8f7a7d 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -60,6 +60,7 @@ + diff --git a/MediaBrowser.ApiInteraction.Metro/DataSerializer.cs b/MediaBrowser.ApiInteraction.Metro/DataSerializer.cs index 6445e8567b..d63e3f0213 100644 --- a/MediaBrowser.ApiInteraction.Metro/DataSerializer.cs +++ b/MediaBrowser.ApiInteraction.Metro/DataSerializer.cs @@ -19,7 +19,6 @@ namespace MediaBrowser.ApiInteraction { if (format == ApiInteraction.SerializationFormats.Protobuf) { - //return Serializer.Deserialize(stream); return ProtobufModelSerializer.Deserialize(stream, null, typeof(T)) as T; } else if (format == ApiInteraction.SerializationFormats.Jsv) @@ -44,7 +43,6 @@ namespace MediaBrowser.ApiInteraction { if (format == ApiInteraction.SerializationFormats.Protobuf) { - //throw new NotImplementedException(); return ProtobufModelSerializer.Deserialize(stream, null, type); } else if (format == ApiInteraction.SerializationFormats.Jsv) diff --git a/MediaBrowser.ApiInteraction.Portable/ApiClient.cs b/MediaBrowser.ApiInteraction.Portable/ApiClient.cs index edadc9f058..f63847e5cd 100644 --- a/MediaBrowser.ApiInteraction.Portable/ApiClient.cs +++ b/MediaBrowser.ApiInteraction.Portable/ApiClient.cs @@ -113,6 +113,23 @@ namespace MediaBrowser.ApiInteraction.Portable GetDataAsync(url, callback); } + /// + /// Gets favorite items + /// + /// The user id. + /// (Optional) Specify a folder Id to localize the search to a specific folder. + public void GetFavoriteItemsAsync(Guid userId, Action callback, Guid? folderId = null) + { + string url = ApiUrl + "/itemlist?listtype=favorites&userId=" + userId.ToString(); + + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + + GetDataAsync(url, callback); + } + /// /// Gets recently added items that are unplayed. /// @@ -143,40 +160,60 @@ namespace MediaBrowser.ApiInteraction.Portable /// /// Gets all items that contain a given Year /// - public void GetItemsWithYearAsync(string name, Guid userId, Action callback) + public void GetItemsWithYearAsync(string name, Guid userId, Action callback, Guid? folderId = null) { string url = ApiUrl + "/itemlist?listtype=itemswithyear&userId=" + userId.ToString() + "&name=" + name; + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + GetDataAsync(url, callback); } /// /// Gets all items that contain a given Genre /// - public void GetItemsWithGenreAsync(string name, Guid userId, Action callback) + public void GetItemsWithGenreAsync(string name, Guid userId, Action callback, Guid? folderId = null) { string url = ApiUrl + "/itemlist?listtype=itemswithgenre&userId=" + userId.ToString() + "&name=" + name; + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + GetDataAsync(url, callback); } /// /// Gets all items that contain a given Person /// - public void GetItemsWithPersonAsync(string name, Guid userId, Action callback) + public void GetItemsWithPersonAsync(string name, Guid userId, Action callback, Guid? folderId = null) { string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name; + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + GetDataAsync(url, callback); } /// /// Gets all items that contain a given Person /// - public void GetItemsWithPersonAsync(string name, string personType, Guid userId, Action callback) + public void GetItemsWithPersonAsync(string name, string personType, Guid userId, Action callback, Guid? folderId = null) { string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name; + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + url += "&persontype=" + personType; GetDataAsync(url, callback); @@ -195,10 +232,15 @@ namespace MediaBrowser.ApiInteraction.Portable /// /// Gets all items that contain a given Studio /// - public void GetItemsWithStudioAsync(string name, Guid userId, Action callback) + public void GetItemsWithStudioAsync(string name, Guid userId, Action callback, Guid? folderId = null) { string url = ApiUrl + "/itemlist?listtype=itemswithstudio&userId=" + userId.ToString() + "&name=" + name; + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + GetDataAsync(url, callback); } @@ -344,6 +386,19 @@ namespace MediaBrowser.ApiInteraction.Portable PostDataAsync(url, formValues, callback, SerializationFormat); } + /// + /// Updates a user's favorite status for an item and returns the updated UserItemData object. + /// + public void UpdateFavoriteStatusAsync(Guid itemId, Guid userId, bool isFavorite, Action callback) + { + string url = ApiUrl + "/favoritestatus?id=" + itemId; + + url += "&userid=" + userId; + url += "&isfavorite=" + (isFavorite ? "1" : "0"); + + GetDataAsync(url, callback); + } + /// /// Performs a GET request, and deserializes the response stream to an object of Type T /// diff --git a/MediaBrowser.ApiInteraction/BaseHttpApiClient.cs b/MediaBrowser.ApiInteraction/BaseHttpApiClient.cs index c3871514e9..3c3daa2091 100644 --- a/MediaBrowser.ApiInteraction/BaseHttpApiClient.cs +++ b/MediaBrowser.ApiInteraction/BaseHttpApiClient.cs @@ -8,6 +8,7 @@ using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; +using MediaBrowser.Model.Entities; namespace MediaBrowser.ApiInteraction { @@ -118,6 +119,26 @@ namespace MediaBrowser.ApiInteraction } } + /// + /// Gets favorite items + /// + /// The user id. + /// (Optional) Specify a folder Id to localize the search to a specific folder. + public async Task GetFavoriteItemsAsync(Guid userId, Guid? folderId = null) + { + string url = ApiUrl + "/itemlist?listtype=favorites&userId=" + userId.ToString(); + + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + + using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) + { + return DeserializeFromStream(stream); + } + } + /// /// Gets recently added items that are unplayed. /// @@ -154,10 +175,16 @@ namespace MediaBrowser.ApiInteraction /// /// Gets all items that contain a given Year /// - public async Task GetItemsWithYearAsync(string name, Guid userId) + /// (Optional) Specify a folder Id to localize the search to a specific folder. + public async Task GetItemsWithYearAsync(string name, Guid userId, Guid? folderId = null) { string url = ApiUrl + "/itemlist?listtype=itemswithyear&userId=" + userId.ToString() + "&name=" + name; + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { return DeserializeFromStream(stream); @@ -167,10 +194,16 @@ namespace MediaBrowser.ApiInteraction /// /// Gets all items that contain a given Genre /// - public async Task GetItemsWithGenreAsync(string name, Guid userId) + /// (Optional) Specify a folder Id to localize the search to a specific folder. + public async Task GetItemsWithGenreAsync(string name, Guid userId, Guid? folderId = null) { string url = ApiUrl + "/itemlist?listtype=itemswithgenre&userId=" + userId.ToString() + "&name=" + name; + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { return DeserializeFromStream(stream); @@ -180,10 +213,16 @@ namespace MediaBrowser.ApiInteraction /// /// Gets all items that contain a given Person /// - public async Task GetItemsWithPersonAsync(string name, Guid userId) + /// (Optional) Specify a folder Id to localize the search to a specific folder. + public async Task GetItemsWithPersonAsync(string name, Guid userId, Guid? folderId = null) { string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name; + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { return DeserializeFromStream(stream); @@ -193,12 +232,18 @@ namespace MediaBrowser.ApiInteraction /// /// Gets all items that contain a given Person /// - public async Task GetItemsWithPersonAsync(string name, string personType, Guid userId) + /// (Optional) Specify a folder Id to localize the search to a specific folder. + public async Task GetItemsWithPersonAsync(string name, string personType, Guid userId, Guid? folderId = null) { string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name; url += "&persontype=" + personType; + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { return DeserializeFromStream(stream); @@ -221,10 +266,16 @@ namespace MediaBrowser.ApiInteraction /// /// Gets all items that contain a given Studio /// - public async Task GetItemsWithStudioAsync(string name, Guid userId) + /// (Optional) Specify a folder Id to localize the search to a specific folder. + public async Task GetItemsWithStudioAsync(string name, Guid userId, Guid? folderId = null) { string url = ApiUrl + "/itemlist?listtype=itemswithstudio&userId=" + userId.ToString() + "&name=" + name; + if (folderId.HasValue) + { + url += "&id=" + folderId.ToString(); + } + using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { return DeserializeFromStream(stream); @@ -387,6 +438,22 @@ namespace MediaBrowser.ApiInteraction } } + /// + /// Updates a user's favorite status for an item and returns the updated UserItemData object. + /// + public async Task UpdateFavoriteStatusAsync(Guid itemId, Guid userId, bool isFavorite) + { + string url = ApiUrl + "/favoritestatus?id=" + itemId; + + url += "&userid=" + userId; + url += "&isfavorite=" + (isFavorite ? "1" : "0"); + + using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) + { + return DeserializeFromStream(stream); + } + } + /// /// Authenticates a user and returns the result /// diff --git a/MediaBrowser.Controller/Resolvers/VideoResolver.cs b/MediaBrowser.Controller/Resolvers/VideoResolver.cs index 1157401af4..90bc658b74 100644 --- a/MediaBrowser.Controller/Resolvers/VideoResolver.cs +++ b/MediaBrowser.Controller/Resolvers/VideoResolver.cs @@ -1,7 +1,7 @@ -using System.ComponentModel.Composition; -using System.IO; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using System.ComponentModel.Composition; +using System.IO; namespace MediaBrowser.Controller.Resolvers { diff --git a/MediaBrowser.Model/DTO/DTOBaseItem.cs b/MediaBrowser.Model/DTO/DTOBaseItem.cs index e1bc9c65b2..c841335242 100644 --- a/MediaBrowser.Model/DTO/DTOBaseItem.cs +++ b/MediaBrowser.Model/DTO/DTOBaseItem.cs @@ -145,7 +145,7 @@ namespace MediaBrowser.Model.DTO /// User data for this item based on the user it's being requested for /// [ProtoMember(40)] - public UserItemData UserData { get; set; } + public DTOUserItemData UserData { get; set; } [ProtoMember(41)] public ItemSpecialCounts SpecialCounts { get; set; } diff --git a/MediaBrowser.Model/DTO/DTOUserItemData.cs b/MediaBrowser.Model/DTO/DTOUserItemData.cs new file mode 100644 index 0000000000..33ff797afd --- /dev/null +++ b/MediaBrowser.Model/DTO/DTOUserItemData.cs @@ -0,0 +1,23 @@ +using ProtoBuf; + +namespace MediaBrowser.Model.DTO +{ + [ProtoContract] + public class DTOUserItemData + { + [ProtoMember(1)] + public float? Rating { get; set; } + + [ProtoMember(2)] + public long PlaybackPositionTicks { get; set; } + + [ProtoMember(3)] + public int PlayCount { get; set; } + + [ProtoMember(4)] + public bool IsFavorite { get; set; } + + [ProtoMember(5)] + public bool? Likes { get; set; } + } +} diff --git a/MediaBrowser.Model/DTO/VideoOutputFormats.cs b/MediaBrowser.Model/DTO/VideoOutputFormats.cs index d69797acc3..3059f6acaf 100644 --- a/MediaBrowser.Model/DTO/VideoOutputFormats.cs +++ b/MediaBrowser.Model/DTO/VideoOutputFormats.cs @@ -2,7 +2,7 @@ namespace MediaBrowser.Model.DTO { /// - /// These are the audio output formats that the api is cabaple of streaming + /// These are the video output formats that the api is cabaple of streaming /// This does not limit the inputs, only the outputs. /// public enum VideoOutputFormats diff --git a/MediaBrowser.Model/Entities/Folder.cs b/MediaBrowser.Model/Entities/Folder.cs index ed3a380889..ec37107c30 100644 --- a/MediaBrowser.Model/Entities/Folder.cs +++ b/MediaBrowser.Model/Entities/Folder.cs @@ -96,6 +96,24 @@ namespace MediaBrowser.Model.Entities return GetParentalAllowedRecursiveChildren(user).Where(f => f.Studios != null && f.Studios.Any(s => s.Equals(studio, StringComparison.OrdinalIgnoreCase))); } + /// + /// Finds all recursive items within a top-level parent that the user has marked as a favorite + /// + public IEnumerable GetFavoriteItems(User user) + { + return GetParentalAllowedRecursiveChildren(user).Where(c => + { + UserItemData data = c.GetUserData(user); + + if (data != null) + { + return data.IsFavorite; + } + + return false; + }); + } + /// /// Finds all recursive items within a top-level parent that contain the given person and are allowed for the current user /// diff --git a/MediaBrowser.Model/Entities/UserItemData.cs b/MediaBrowser.Model/Entities/UserItemData.cs index d7b692014a..b342b9583f 100644 --- a/MediaBrowser.Model/Entities/UserItemData.cs +++ b/MediaBrowser.Model/Entities/UserItemData.cs @@ -1,25 +1,67 @@ using System; -using ProtoBuf; +using System.Runtime.Serialization; namespace MediaBrowser.Model.Entities { - [ProtoContract] public class UserItemData { - [ProtoMember(1)] - public UserItemRating Rating { get; set; } + private float? _Rating = null; + /// + /// Gets or sets the users 0-10 rating + /// + public float? Rating + { + get + { + return _Rating; + } + set + { + if (value.HasValue) + { + if (value.Value < 0 || value.Value > 10) + { + throw new InvalidOperationException("A 0-10 rating is required for UserItemData."); + } + } + + _Rating = value; + } + } - [ProtoMember(2)] public long PlaybackPositionTicks { get; set; } - [ProtoMember(3)] public int PlayCount { get; set; } - } - public enum UserItemRating - { - Likes, - Dislikes, - Favorite + public bool IsFavorite { get; set; } + + /// + /// This is an interpreted property to indicate likes or dislikes + /// This should never be serialized. + /// + [IgnoreDataMember] + public bool? Likes + { + get + { + if (Rating != null) + { + return Rating >= 6.5; + } + + return null; + } + set + { + if (value.HasValue) + { + Rating = value.Value ? 10 : 1; + } + else + { + Rating = null; + } + } + } } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index ae8b686c2e..b6fc3c981e 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -36,6 +36,7 @@ +