From ab812b7c7e63ffed34db9105ec9fcfd02bf051e7 Mon Sep 17 00:00:00 2001 From: yuvve <37124628+yuvve@users.noreply.github.com> Date: Tue, 1 Oct 2024 09:19:07 +0200 Subject: [PATCH] Add watchlist feature * Added watchlist property, sorting, collections and repo Co-Authored-By: Simon Larsson * Bugfix Made bool nullable, fixed sql typo Co-Authored-By: Simon Larsson * Fixed copy/paste error --------- Co-authored-by: Simon Larsson Endpoints for watchlist add and remove (#4) * Endpoints for watchlist add and remove Co-Authored-By: Simon Larsson <99037566+simlar-0@users.noreply.github.com> * Fixed blank line errors --------- Co-authored-by: Simon Larsson <99037566+simlar-0@users.noreply.github.com> Remove watchlist status when watched (#6) Add @simlar-0 and @yuvve to CONTRIBUTORS.md Fixed Null handling of isWatchlisted (#8) Fix typo (#10) Co-Authored-By: Simon Larsson --- CONTRIBUTORS.md | 3 + .../Data/SqliteItemRepository.cs | 21 ++++- .../Data/SqliteUserDataRepository.cs | 20 +++-- .../Library/UserDataManager.cs | 11 +++ .../Sorting/IsWatchlistedComparer.cs | 61 ++++++++++++++ Jellyfin.Api/Controllers/GenresController.cs | 3 + Jellyfin.Api/Controllers/ItemsController.cs | 7 ++ .../Controllers/TrailersController.cs | 1 + .../Controllers/UserLibraryController.cs | 81 +++++++++++++++++++ Jellyfin.Data/Enums/CollectionType.cs | 20 ++++- Jellyfin.Data/Enums/ItemSortBy.cs | 5 ++ Jellyfin.Data/Enums/ViewType.cs | 7 +- MediaBrowser.Controller/Entities/BaseItem.cs | 7 ++ .../Entities/InternalItemsQuery.cs | 2 + .../Entities/UserItemData.cs | 6 ++ .../Entities/UserViewBuilder.cs | 42 ++++++++++ .../Dto/UpdateUserItemDataDto.cs | 6 ++ MediaBrowser.Model/Dto/UserItemDataDto.cs | 6 ++ MediaBrowser.Model/Querying/ItemFilter.cs | 7 +- .../Savers/BaseNfoSaver.cs | 6 ++ 20 files changed, 312 insertions(+), 10 deletions(-) create mode 100644 Emby.Server.Implementations/Sorting/IsWatchlistedComparer.cs diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a9deb1c4a2e..ae01a8200ab 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -192,6 +192,9 @@ - [jaina heartles](https://github.com/heartles) - [oxixes](https://github.com/oxixes) - [elfalem](https://github.com/elfalem) + - [simlar-0](https://github.com/simlar-0) + - [yuvve](https://github.com/yuvve) + # Emby Contributors diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 3477194cf70..63a9f83a5c1 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2044,11 +2044,13 @@ private static bool EnableJoinUserData(InternalItemsQuery query) || sortingFields.Contains(ItemSortBy.PlayCount) || sortingFields.Contains(ItemSortBy.DatePlayed) || sortingFields.Contains(ItemSortBy.SeriesDatePlayed) + || sortingFields.Contains(ItemSortBy.IsWatchlisted) || query.IsFavoriteOrLiked.HasValue || query.IsFavorite.HasValue || query.IsResumable.HasValue || query.IsPlayed.HasValue - || query.IsLiked.HasValue; + || query.IsLiked.HasValue + || query.IsWatchlisted.HasValue; } private bool HasField(InternalItemsQuery query, ItemFields name) @@ -2268,6 +2270,7 @@ private void SetFinalColumnsToSelect(InternalItemsQuery query, List colu columns.Add("UserDatas.playbackPositionTicks"); columns.Add("UserDatas.playcount"); columns.Add("UserDatas.isFavorite"); + columns.Add("UserDatas.isWatchlisted"); columns.Add("UserDatas.played"); columns.Add("UserDatas.rating"); } @@ -2853,6 +2856,7 @@ private string MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query) ItemSortBy.DatePlayed => "LastPlayedDate", ItemSortBy.PlayCount => "PlayCount", ItemSortBy.IsFavoriteOrLiked => "(Select Case When IsFavorite is null Then 0 Else IsFavorite End )", + ItemSortBy.IsWatchlisted => "isWatchlisted", ItemSortBy.IsFolder => "IsFolder", ItemSortBy.IsPlayed => "played", ItemSortBy.IsUnplayed => "played", @@ -3515,6 +3519,20 @@ private List GetWhereClauses(InternalItemsQuery query, SqliteCommand? st statement?.TryBind("@IsFavorite", query.IsFavorite.Value); } + if (query.IsWatchlisted.HasValue) + { + if (query.IsWatchlisted.Value) + { + whereClauses.Add("IsWatchlisted=@IsWatchlisted"); + } + else + { + whereClauses.Add("(IsWatchlisted is null or IsWatchlisted=@IsWatchlisted)"); + } + + statement?.TryBind("@IsWatchlisted", query.IsWatchlisted.Value); + } + if (EnableJoinUserData(query)) { if (query.IsPlayed.HasValue) @@ -4923,6 +4941,7 @@ private List GetItemValueNames(int[] itemValueTypes, IReadOnlyList GetAllUserData(long userId) using (var connection = GetConnection()) { - using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId")) + using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,isWatchlisted,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId")) { statement.TryBind("@UserId", userId); @@ -363,6 +366,11 @@ private UserItemData ReadRow(SqliteDataReader reader) userData.SubtitleStreamIndex = subtitleStreamIndex; } + if (reader.TryGetBoolean(10, out var isWatchlisted)) + { + userData.IsWatchlisted = isWatchlisted; + } + return userData; } } diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 62d22b23ff1..e1af8d139f2 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -104,6 +104,11 @@ public void SaveUserData(User user, BaseItem item, UpdateUserItemDataDto userDat userData.IsFavorite = userDataDto.IsFavorite.Value; } + if (userDataDto.IsWatchlisted.HasValue) + { + userData.IsWatchlisted = userDataDto.IsWatchlisted.Value; + } + if (userDataDto.Likes.HasValue) { userData.Likes = userDataDto.Likes.Value; @@ -198,6 +203,7 @@ private UserItemDataDto GetUserItemDataDto(UserItemData data) return new UserItemDataDto { IsFavorite = data.IsFavorite, + IsWatchlisted = data.IsWatchlisted, Likes = data.Likes, PlaybackPositionTicks = data.PlaybackPositionTicks, PlayCount = data.PlayCount, @@ -282,6 +288,11 @@ public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPosi data.PlaybackPositionTicks = positionTicks; + if (playedToCompletion) + { + data.IsWatchlisted = false; + } + return playedToCompletion; } } diff --git a/Emby.Server.Implementations/Sorting/IsWatchlistedComparer.cs b/Emby.Server.Implementations/Sorting/IsWatchlistedComparer.cs new file mode 100644 index 00000000000..111351f7c38 --- /dev/null +++ b/Emby.Server.Implementations/Sorting/IsWatchlistedComparer.cs @@ -0,0 +1,61 @@ +#nullable disable + +#pragma warning disable CS1591 + +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Implementations.Sorting +{ + public class IsWatchlistedComparer : IUserBaseItemComparer + { + /// + /// Gets or sets the user. + /// + /// The user. + public User User { get; set; } + + /// + /// Gets the name. + /// + /// The name. + public ItemSortBy Type => ItemSortBy.IsWatchlisted; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + + /// + /// Compares the specified x. + /// + /// The x. + /// The y. + /// System.Int32. + public int Compare(BaseItem x, BaseItem y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + /// + /// Gets the date. + /// + /// The x. + /// DateTime. + private int GetValue(BaseItem x) + { + return x.IsWatchlisted(User) ? 0 : 1; + } + } +} diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 54d48aec21c..15d92679ad3 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -57,6 +57,7 @@ public GenresController( /// Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited. /// Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited. /// Optional filter by items that are marked as favorite, or not. + /// Optional filter by items that are marked as watchlisted, or not. /// Optional, the max number of images to return, per image type. /// Optional. The image types to include in the output. /// User id. @@ -80,6 +81,7 @@ public ActionResult> GetGenres( [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, + [FromQuery] bool? isWatchlisted, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery] Guid? userId, @@ -109,6 +111,7 @@ public ActionResult> GetGenres( StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, + IsWatchlisted = isWatchlisted, NameLessThan = nameLessThan, NameStartsWith = nameStartsWith, NameStartsWithOrGreater = nameStartsWithOrGreater, diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 828bd517405..19516c9c779 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -112,6 +112,7 @@ public ItemsController( /// Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. /// Optional filter by items that are marked as favorite, or not. + /// Optional filter by items that are watchlisted, or not. /// Optional filter by MediaType. Allows multiple, comma delimited. /// Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited. /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime. @@ -202,6 +203,7 @@ public ActionResult> GetItems( [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, + [FromQuery] bool? isWatchlisted, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, @@ -316,6 +318,7 @@ public ActionResult> GetItems( Recursive = recursive ?? false, OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), IsFavorite = isFavorite, + IsWatchlisted = isWatchlisted, Limit = limit, StartIndex = startIndex, IsMissing = isMissing, @@ -416,6 +419,9 @@ public ActionResult> GetItems( case ItemFilter.Likes: query.IsLiked = true; break; + case ItemFilter.IsWatchlisted: + query.IsWatchlisted = true; + break; } } @@ -756,6 +762,7 @@ public ActionResult> GetItemsByUserIdLegacy( includeItemTypes, filters, isFavorite, + null, mediaTypes, imageTypes, sortBy, diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index d7d0cc4544a..79c6deb63aa 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -251,6 +251,7 @@ public ActionResult> GetTrailers( includeItemTypes, filters, isFavorite, + null, mediaTypes, imageTypes, sortBy, diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index e7bf717274d..005e77df820 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -327,6 +327,68 @@ public ActionResult DeleteUserItemRating( return UpdateUserItemRatingInternal(user, item, null); } + /// + /// Adds an item to the user's watchlist. + /// + /// User id. + /// Item id. + /// Item added to watchlist. + /// An containing the . + [HttpPost("UserWatchlistItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult AddToWatchlistItem( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = itemId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById(itemId, user); + if (item is null) + { + return NotFound(); + } + + return MarkWatchlist(user, item, true); + } + + /// + /// Removes an item from the user's watchlist. + /// + /// User id. + /// Item id. + /// Item removed from watchlist. + /// An containing the . + [HttpDelete("UserWatchlistItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult RemoveFromWatchlist( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) + { + userId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + return NotFound(); + } + + var item = itemId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById(itemId, user); + if (item is null) + { + return NotFound(); + } + + return MarkWatchlist(user, item, false); + } + /// /// Deletes a user's saved personal rating for an item. /// @@ -670,6 +732,25 @@ private UserItemDataDto MarkFavorite(User user, BaseItem item, bool isFavorite) return _userDataRepository.GetUserDataDto(item, user); } + /// + /// Adds item to watchlist. + /// + /// The user. + /// The item. + /// if set to true [is in watchlist]. + private UserItemDataDto MarkWatchlist(User user, BaseItem item, bool isWatchlisted) + { + // Get the user data for this item + var data = _userDataRepository.GetUserData(user, item); + + // Set watchlist status + data.IsWatchlisted = isWatchlisted; + + _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); + + return _userDataRepository.GetUserDataDto(item, user); + } + /// /// Updates the user item rating. /// diff --git a/Jellyfin.Data/Enums/CollectionType.cs b/Jellyfin.Data/Enums/CollectionType.cs index e3d3b07afeb..1b6794cd425 100644 --- a/Jellyfin.Data/Enums/CollectionType.cs +++ b/Jellyfin.Data/Enums/CollectionType.cs @@ -161,5 +161,23 @@ public enum CollectionType /// Movie genre collection. /// [OpenApiIgnoreEnum] - moviegenre = 115 + moviegenre = 115, + + /// + /// Tv watchlist series collection. + /// + [OpenApiIgnoreEnum] + tvwatchlistseries = 116, + + /// + /// Tv watchlist episodes collection. + /// + [OpenApiIgnoreEnum] + tvwatchlistepisodes = 117, + + /// + /// Movie watchlist collection. + /// + [OpenApiIgnoreEnum] + moviewatchlist = 118, } diff --git a/Jellyfin.Data/Enums/ItemSortBy.cs b/Jellyfin.Data/Enums/ItemSortBy.cs index 17bf1166dea..4b454164715 100644 --- a/Jellyfin.Data/Enums/ItemSortBy.cs +++ b/Jellyfin.Data/Enums/ItemSortBy.cs @@ -164,4 +164,9 @@ public enum ItemSortBy /// The search score. /// SearchScore = 31, + + /// + /// The IsWatchlisted boolean. + /// + IsWatchlisted = 32, } diff --git a/Jellyfin.Data/Enums/ViewType.cs b/Jellyfin.Data/Enums/ViewType.cs index c0fd7d448bf..d8f1ea2b290 100644 --- a/Jellyfin.Data/Enums/ViewType.cs +++ b/Jellyfin.Data/Enums/ViewType.cs @@ -108,6 +108,11 @@ public enum ViewType /// /// Shows upcoming. /// - Upcoming = 20 + Upcoming = 20, + + /// + /// Shows watchlist. + /// + Watchlist = 21, } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index eb605f6c873..0af9cf894bc 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2282,6 +2282,13 @@ public bool IsFavoriteOrLiked(User user) return userdata is not null && (userdata.IsFavorite || (userdata.Likes ?? false)); } + public bool IsWatchlisted(User user) + { + var userdata = UserDataManager.GetUserData(user, this); + + return userdata is not null && userdata.IsWatchlisted; + } + public virtual bool IsUnplayed(User user) { ArgumentNullException.ThrowIfNull(user); diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 1461a3680ac..abc23453ab3 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -79,6 +79,8 @@ public InternalItemsQuery(User? user) public bool? IsFavoriteOrLiked { get; set; } + public bool? IsWatchlisted { get; set; } + public bool? IsLiked { get; set; } public bool? IsPlayed { get; set; } diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index 15bd41a9c3c..1c228468bcc 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -63,6 +63,12 @@ public double? Rating /// true if this instance is favorite; otherwise, false. public bool IsFavorite { get; set; } + /// + /// Gets or sets a value indicating whether this instance is watchlisted. + /// + /// true if this instance is watchlisted; otherwise, false. + public bool IsWatchlisted { get; set; } + /// /// Gets or sets the last played date. /// diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 420349f35ca..d5f4e22f1e1 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -88,6 +88,9 @@ public QueryResult GetUserItems(Folder queryParent, Folder displayPare case CollectionType.moviefavorites: return GetFavoriteMovies(queryParent, user, query); + case CollectionType.moviewatchlist: + return GetWatchlistMovies(queryParent, user, query); + case CollectionType.movielatest: return GetMovieLatest(queryParent, user, query); @@ -109,9 +112,15 @@ public QueryResult GetUserItems(Folder queryParent, Folder displayPare case CollectionType.tvfavoriteepisodes: return GetFavoriteEpisodes(queryParent, user, query); + case CollectionType.tvwatchlistepisodes: + return GetWatchlistEpisodes(queryParent, user, query); + case CollectionType.tvfavoriteseries: return GetFavoriteSeries(queryParent, user, query); + case CollectionType.tvwatchlistseries: + return GetWatchlistSeries(queryParent, user, query); + default: { if (queryParent is UserView) @@ -190,6 +199,39 @@ private QueryResult GetFavoriteEpisodes(Folder parent, User user, Inte return _libraryManager.GetItemsResult(query); } + private QueryResult GetWatchlistMovies(Folder parent, User user, InternalItemsQuery query) + { + query.Recursive = true; + query.Parent = parent; + query.SetUser(user); + query.IsWatchlisted = true; + query.IncludeItemTypes = new[] { BaseItemKind.Movie }; + + return _libraryManager.GetItemsResult(query); + } + + private QueryResult GetWatchlistEpisodes(Folder parent, User user, InternalItemsQuery query) + { + query.Recursive = true; + query.Parent = parent; + query.SetUser(user); + query.IsWatchlisted = true; + query.IncludeItemTypes = new[] { BaseItemKind.Episode }; + + return _libraryManager.GetItemsResult(query); + } + + private QueryResult GetWatchlistSeries(Folder parent, User user, InternalItemsQuery query) + { + query.Recursive = true; + query.Parent = parent; + query.SetUser(user); + query.IsWatchlisted = true; + query.IncludeItemTypes = new[] { BaseItemKind.Series }; + + return _libraryManager.GetItemsResult(query); + } + private QueryResult GetMovieMovies(Folder parent, User user, InternalItemsQuery query) { query.Recursive = true; diff --git a/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs b/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs index 7bfedf97352..d49cddedbef 100644 --- a/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs +++ b/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs @@ -43,6 +43,12 @@ public class UpdateUserItemDataDto /// true if this instance is favorite; otherwise, false. public bool? IsFavorite { get; set; } + /// + /// Gets or sets a value indicating whether this instance is watchlisted. + /// + /// true if this instance is favorite; otherwise, false. + public bool? IsWatchlisted { get; set; } + /// /// Gets or sets a value indicating whether this is likes. /// diff --git a/MediaBrowser.Model/Dto/UserItemDataDto.cs b/MediaBrowser.Model/Dto/UserItemDataDto.cs index 3bb45a0e047..56e0d9b27e1 100644 --- a/MediaBrowser.Model/Dto/UserItemDataDto.cs +++ b/MediaBrowser.Model/Dto/UserItemDataDto.cs @@ -43,6 +43,12 @@ public class UserItemDataDto /// true if this instance is favorite; otherwise, false. public bool IsFavorite { get; set; } + /// + /// Gets or sets a value indicating whether this instance is watchlisted. + /// + /// true if this instance is favorite; otherwise, false. + public bool IsWatchlisted { get; set; } + /// /// Gets or sets a value indicating whether this is likes. /// diff --git a/MediaBrowser.Model/Querying/ItemFilter.cs b/MediaBrowser.Model/Querying/ItemFilter.cs index 0ebb5185f72..3cc02755b76 100644 --- a/MediaBrowser.Model/Querying/ItemFilter.cs +++ b/MediaBrowser.Model/Querying/ItemFilter.cs @@ -48,6 +48,11 @@ public enum ItemFilter /// /// The is favorite or likes. /// - IsFavoriteOrLikes = 10 + IsFavoriteOrLikes = 10, + + /// + /// The item is watchlisted. + /// + IsWatchlisted = 11, } } diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 2afec3f6cdd..97703eb639c 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -96,6 +96,8 @@ public abstract partial class BaseNfoSaver : IMetadataFileSaver "isuserfavorite", "userrating", + "iswatchlisted", + "countrycode" }; @@ -880,6 +882,10 @@ private void AddUserData(BaseItem item, XmlWriter writer, IUserManager userManag userdata.Rating.Value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); } + writer.WriteElementString( + "iswatchlisted", + userdata.IsWatchlisted.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); + if (!item.IsFolder) { writer.WriteElementString(