diff --git a/MediaBrowser.Api/ApiService.cs b/MediaBrowser.Api/ApiService.cs
index 895ca3d19..b3c623739 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 000000000..851120230
--- /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 483feb2fb..024647d8a 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 f1d5d0c37..9845a5b21 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 4016a88a3..31bb8f7a7 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 6445e8567..d63e3f021 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 edadc9f05..f63847e5c 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,29 +160,29 @@ 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;
- GetDataAsync(url, callback);
- }
-
- ///
- /// Gets all items that contain a given Person
- ///
- public void GetItemsWithPersonAsync(string name, Guid userId, Action callback)
- {
- string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name;
+ if (folderId.HasValue)
+ {
+ url += "&id=" + folderId.ToString();
+ }
GetDataAsync(url, callback);
}
@@ -173,10 +190,30 @@ namespace MediaBrowser.ApiInteraction.Portable
///
/// Gets all items that contain a given Person
///
- public void GetItemsWithPersonAsync(string name, string personType, 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, 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 c3871514e..3c3daa209 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 1157401af..90bc658b7 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 e1bc9c65b..c84133524 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 000000000..33ff797af
--- /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 d69797acc..3059f6aca 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 ed3a38088..ec37107c3 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 d7b692014..b342b9583 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 ae8b686c2..b6fc3c981 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -36,6 +36,7 @@
+