Skip to content

Commit

Permalink
Add watchlist feature
Browse files Browse the repository at this point in the history
* Added watchlist property, sorting, collections and repo

Co-Authored-By: Simon Larsson <[email protected]>

* Bugfix

Made bool nullable, fixed sql typo

Co-Authored-By: Simon Larsson <[email protected]>

* Fixed copy/paste error

---------

Co-authored-by: Simon Larsson <[email protected]>

Endpoints for watchlist add and remove (#4)

* Endpoints for watchlist add and remove

Co-Authored-By: Simon Larsson <[email protected]>

* Fixed blank line errors

---------

Co-authored-by: Simon Larsson <[email protected]>

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 <[email protected]>
  • Loading branch information
yuvve and simlar-0 committed Oct 4, 2024
1 parent b496f97 commit ab812b7
Show file tree
Hide file tree
Showing 20 changed files with 312 additions and 10 deletions.
3 changes: 3 additions & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 20 additions & 1 deletion Emby.Server.Implementations/Data/SqliteItemRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -2268,6 +2270,7 @@ private void SetFinalColumnsToSelect(InternalItemsQuery query, List<string> 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");
}
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -3515,6 +3519,20 @@ private List<string> 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)
Expand Down Expand Up @@ -4923,6 +4941,7 @@ private List<string> GetItemValueNames(int[] itemValueTypes, IReadOnlyList<strin
{
IsPlayed = query.IsPlayed,
IsFavorite = query.IsFavorite,
IsWatchlisted = query.IsWatchlisted,
IsFavoriteOrLiked = query.IsFavoriteOrLiked,
IsLiked = query.IsLiked,
IsLocked = query.IsLocked,
Expand Down
20 changes: 14 additions & 6 deletions Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,21 @@ public override void Initialize()
using var transaction = connection.BeginTransaction();
connection.Execute(string.Join(
';',
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT, isWatchlisted bit not null)",
"drop index if exists idx_userdata",
"drop index if exists idx_userdata1",
"drop index if exists idx_userdata2",
"drop index if exists userdataindex1",
"drop index if exists userdataindex",
"drop index if exists userdataindex3",
"drop index if exists userdataindex4",
"drop index if exists userdataindex5",
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)",
"create index if not exists UserDatasIndex5 on UserDatas (key, userId, lastPlayedDate)"));
"create index if not exists UserDatasIndex5 on UserDatas (key, userId, lastPlayedDate)",
"create index if not exists UserDatasIndex6 on UserDatas (key, userId, isWatchlisted)"));

if (!userDataTableExists)
{
Expand All @@ -80,7 +82,7 @@ public override void Initialize()

ImportUserIds(connection, users);

connection.Execute("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
connection.Execute("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, isWatchlisted, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, isWatchlisted, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");

transaction.Commit();
}
Expand Down Expand Up @@ -178,7 +180,7 @@ public void PersistUserData(long internalUserId, string key, UserItemData userDa

private static void SaveUserData(ManagedConnection db, long internalUserId, string key, UserItemData userData)
{
using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)"))
using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,isWatchlisted,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite, @isWatchlisted,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)"))
{
statement.TryBind("@userId", internalUserId);
statement.TryBind("@key", key);
Expand All @@ -195,6 +197,7 @@ private static void SaveUserData(ManagedConnection db, long internalUserId, stri
statement.TryBind("@played", userData.Played);
statement.TryBind("@playCount", userData.PlayCount);
statement.TryBind("@isFavorite", userData.IsFavorite);
statement.TryBind("@isWatchlisted", userData.IsWatchlisted);
statement.TryBind("@playbackPositionTicks", userData.PlaybackPositionTicks);

if (userData.LastPlayedDate.HasValue)
Expand Down Expand Up @@ -269,7 +272,7 @@ public UserItemData GetUserData(long userId, string key)

using (var connection = GetConnection(true))
{
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,isWatchlisted,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
{
statement.TryBind("@UserId", userId);
statement.TryBind("@Key", key);
Expand Down Expand Up @@ -312,7 +315,7 @@ public List<UserItemData> 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);

Expand Down Expand Up @@ -363,6 +366,11 @@ private UserItemData ReadRow(SqliteDataReader reader)
userData.SubtitleStreamIndex = subtitleStreamIndex;
}

if (reader.TryGetBoolean(10, out var isWatchlisted))
{
userData.IsWatchlisted = isWatchlisted;
}

return userData;
}
}
Expand Down
11 changes: 11 additions & 0 deletions Emby.Server.Implementations/Library/UserDataManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -282,6 +288,11 @@ public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPosi

data.PlaybackPositionTicks = positionTicks;

if (playedToCompletion)
{
data.IsWatchlisted = false;
}

return playedToCompletion;
}
}
Expand Down
61 changes: 61 additions & 0 deletions Emby.Server.Implementations/Sorting/IsWatchlistedComparer.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Gets or sets the user.
/// </summary>
/// <value>The user.</value>
public User User { get; set; }

/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public ItemSortBy Type => ItemSortBy.IsWatchlisted;

/// <summary>
/// Gets or sets the user data repository.
/// </summary>
/// <value>The user data repository.</value>
public IUserDataManager UserDataRepository { get; set; }

/// <summary>
/// Gets or sets the user manager.
/// </summary>
/// <value>The user manager.</value>
public IUserManager UserManager { get; set; }

/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}

/// <summary>
/// Gets the date.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
return x.IsWatchlisted(User) ? 0 : 1;
}
}
}
3 changes: 3 additions & 0 deletions Jellyfin.Api/Controllers/GenresController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public GenresController(
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
/// <param name="isWatchlisted">Optional filter by items that are marked as watchlisted, or not.</param>
/// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <param name="userId">User id.</param>
Expand All @@ -80,6 +81,7 @@ public ActionResult<QueryResult<BaseItemDto>> 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,
Expand Down Expand Up @@ -109,6 +111,7 @@ public ActionResult<QueryResult<BaseItemDto>> GetGenres(
StartIndex = startIndex,
Limit = limit,
IsFavorite = isFavorite,
IsWatchlisted = isWatchlisted,
NameLessThan = nameLessThan,
NameStartsWith = nameStartsWith,
NameStartsWithOrGreater = nameStartsWithOrGreater,
Expand Down
7 changes: 7 additions & 0 deletions Jellyfin.Api/Controllers/ItemsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public ItemsController(
/// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.</param>
/// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
/// <param name="isWatchlisted">Optional filter by items that are watchlisted, or not.</param>
/// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
/// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.</param>
/// <param name="sortBy">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.</param>
Expand Down Expand Up @@ -202,6 +203,7 @@ public ActionResult<QueryResult<BaseItemDto>> 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,
Expand Down Expand Up @@ -316,6 +318,7 @@ public ActionResult<QueryResult<BaseItemDto>> GetItems(
Recursive = recursive ?? false,
OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
IsFavorite = isFavorite,
IsWatchlisted = isWatchlisted,
Limit = limit,
StartIndex = startIndex,
IsMissing = isMissing,
Expand Down Expand Up @@ -416,6 +419,9 @@ public ActionResult<QueryResult<BaseItemDto>> GetItems(
case ItemFilter.Likes:
query.IsLiked = true;
break;
case ItemFilter.IsWatchlisted:
query.IsWatchlisted = true;
break;
}
}

Expand Down Expand Up @@ -756,6 +762,7 @@ public ActionResult<QueryResult<BaseItemDto>> GetItemsByUserIdLegacy(
includeItemTypes,
filters,
isFavorite,
null,
mediaTypes,
imageTypes,
sortBy,
Expand Down
1 change: 1 addition & 0 deletions Jellyfin.Api/Controllers/TrailersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ public ActionResult<QueryResult<BaseItemDto>> GetTrailers(
includeItemTypes,
filters,
isFavorite,
null,
mediaTypes,
imageTypes,
sortBy,
Expand Down
Loading

0 comments on commit ab812b7

Please sign in to comment.