diff options
40 files changed, 396 insertions, 193 deletions
diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml index b35a79efc..89f9c59d7 100644 --- a/.github/workflows/ci-codeql-analysis.yml +++ b/.github/workflows/ci-codeql-analysis.yml @@ -27,11 +27,11 @@ jobs: dotnet-version: '8.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@c0d1daa7f7e14667747d73a7dbbe8c074bc8bfe2 # v2.22.9 + uses: github/codeql-action/init@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@c0d1daa7f7e14667747d73a7dbbe8c074bc8bfe2 # v2.22.9 + uses: github/codeql-action/autobuild@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c0d1daa7f7e14667747d73a7dbbe8c074bc8bfe2 # v2.22.9 + uses: github/codeql-action/analyze@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11 diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index 7d6667794..5ff9820cb 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -25,7 +25,7 @@ jobs: - name: Generate openapi.json run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" - name: Upload openapi.json - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: openapi-head retention-days: 14 @@ -59,7 +59,7 @@ jobs: - name: Generate openapi.json run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" - name: Upload openapi.json - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: openapi-base retention-days: 14 @@ -78,12 +78,12 @@ jobs: - openapi-base steps: - name: Download openapi-head - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: openapi-head path: openapi-head - name: Download openapi-base - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 with: name: openapi-base path: openapi-base diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 5a0125f5f..0dacbc5c6 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -19,9 +19,9 @@ jobs: runs-on: "${{ matrix.os }}" steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4 + - uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 with: dotnet-version: ${{ env.SDK_VERSION }} @@ -34,7 +34,7 @@ jobs: --verbosity minimal - name: Merge code coverage results - uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5 + uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5.2.0 with: reports: "**/coverage.cobertura.xml" targetdir: "merged/" @@ -42,9 +42,3 @@ jobs: # TODO - which action / tool to use to publish code coverage results? # - name: Publish code coverage results - - - name: Publish OpenAPI Artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 - with: - name: "OpenAPI Spec" - path: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net*/openapi.json" diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 6e8f77977..34c722e41 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -32,26 +32,26 @@ namespace Emby.Server.Implementations.Images switch (viewType) { - case CollectionType.Movies: + case CollectionType.movies: includeItemTypes = new[] { BaseItemKind.Movie }; break; - case CollectionType.TvShows: + case CollectionType.tvshows: includeItemTypes = new[] { BaseItemKind.Series }; break; - case CollectionType.Music: + case CollectionType.music: includeItemTypes = new[] { BaseItemKind.MusicAlbum }; break; - case CollectionType.MusicVideos: + case CollectionType.musicvideos: includeItemTypes = new[] { BaseItemKind.MusicVideo }; break; - case CollectionType.Books: + case CollectionType.books: includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook }; break; - case CollectionType.BoxSets: + case CollectionType.boxsets: includeItemTypes = new[] { BaseItemKind.BoxSet }; break; - case CollectionType.HomeVideos: - case CollectionType.Photos: + case CollectionType.homevideos: + case CollectionType.photos: includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo }; break; default: @@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Images break; } - var recursive = viewType != CollectionType.Playlists; + var recursive = viewType != CollectionType.playlists; return view.GetItemList(new InternalItemsQuery { diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 5de53df73..6b2ae23b3 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Images var view = (UserView)item; var isUsingCollectionStrip = IsUsingCollectionStrip(view); - var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.BoxSets && view.ViewType != CollectionType.Playlists; + var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.boxsets && view.ViewType != CollectionType.playlists; var result = view.GetItemList(new InternalItemsQuery { @@ -114,9 +114,9 @@ namespace Emby.Server.Implementations.Images { CollectionType[] collectionStripViewTypes = { - CollectionType.Movies, - CollectionType.TvShows, - CollectionType.Playlists + CollectionType.movies, + CollectionType.tvshows, + CollectionType.playlists }; return view?.ViewType is not null && collectionStripViewTypes.Contains(view.ViewType.Value); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f40177fa7..a79ffd9cb 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1514,7 +1514,7 @@ namespace Emby.Server.Implementations.Library { if (item is UserView view) { - if (view.ViewType == CollectionType.LiveTv) + if (view.ViewType == CollectionType.livetv) { return new[] { view.Id }; } @@ -1543,7 +1543,7 @@ namespace Emby.Server.Implementations.Library } // Handle grouping - if (user is not null && view.ViewType != CollectionType.Unknown && UserView.IsEligibleForGrouping(view.ViewType) + if (user is not null && view.ViewType != CollectionType.unknown && UserView.IsEligibleForGrouping(view.ViewType) && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0) { return GetUserRootFolder() diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index ac423ed09..dbf05c1db 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio List<FileSystemMetadata> files, CollectionType? collectionType) { - if (collectionType == CollectionType.Books) + if (collectionType == CollectionType.books) { return ResolveMultipleAudio(parent, files, true); } @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var collectionType = args.GetCollectionType(); - var isBooksCollectionType = collectionType == CollectionType.Books; + var isBooksCollectionType = collectionType == CollectionType.books; if (args.IsDirectory) { @@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio MediaBrowser.Controller.Entities.Audio.Audio item = null; - var isMusicCollectionType = collectionType == CollectionType.Music; + var isMusicCollectionType = collectionType == CollectionType.music; // Use regular audio type for mixed libraries, owned items and music if (isMixedCollectionType || diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 06e292f4c..0bfb7fbe6 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio protected override MusicAlbum Resolve(ItemResolveArgs args) { var collectionType = args.GetCollectionType(); - var isMusicMediaFolder = collectionType == CollectionType.Music; + var isMusicMediaFolder = collectionType == CollectionType.music; // If there's a collection type and it's not music, don't allow it. if (!isMusicMediaFolder) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 7d6f97b12..1bdae7f62 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var collectionType = args.GetCollectionType(); - var isMusicMediaFolder = collectionType == CollectionType.Music; + var isMusicMediaFolder = collectionType == CollectionType.music; // If there's a collection type and it's not music, it can't be a music artist if (!isMusicMediaFolder) diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index b76bfe427..464a548ab 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books var collectionType = args.GetCollectionType(); // Only process items that are in a collection folder containing books - if (collectionType != CollectionType.Books) + if (collectionType != CollectionType.books) { return null; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 50fd8b877..1a210e3cc 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -31,11 +31,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private static readonly CollectionType[] _validCollectionTypes = new[] { - CollectionType.Movies, - CollectionType.HomeVideos, - CollectionType.MusicVideos, - CollectionType.TvShows, - CollectionType.Photos + CollectionType.movies, + CollectionType.homevideos, + CollectionType.musicvideos, + CollectionType.tvshows, + CollectionType.photos }; /// <summary> @@ -100,12 +100,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies Video movie = null; var files = args.GetActualFileSystemChildren().ToList(); - if (collectionType == CollectionType.MusicVideos) + if (collectionType == CollectionType.musicvideos) { movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); } - if (collectionType == CollectionType.HomeVideos) + if (collectionType == CollectionType.homevideos) { movie = FindMovie<Video>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); } @@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true); } - if (collectionType == CollectionType.Movies) + if (collectionType == CollectionType.movies) { movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true); } @@ -147,17 +147,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies Video item = null; - if (collectionType == CollectionType.MusicVideos) + if (collectionType == CollectionType.musicvideos) { item = ResolveVideo<MusicVideo>(args, false); } // To find a movie file, the collection type must be movies or boxsets - else if (collectionType == CollectionType.Movies) + else if (collectionType == CollectionType.movies) { item = ResolveVideo<Movie>(args, true); } - else if (collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos) + else if (collectionType == CollectionType.homevideos || collectionType == CollectionType.photos) { item = ResolveVideo<Video>(args, false); } @@ -195,12 +195,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } - if (collectionType is CollectionType.MusicVideos) + if (collectionType is CollectionType.musicvideos) { return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false); } - if (collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos) + if (collectionType == CollectionType.homevideos || collectionType == CollectionType.photos) { return ResolveVideos<Video>(parent, files, false, collectionType, false); } @@ -221,12 +221,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return ResolveVideos<Movie>(parent, files, false, collectionType, true); } - if (collectionType == CollectionType.Movies) + if (collectionType == CollectionType.movies) { return ResolveVideos<Movie>(parent, files, true, collectionType, true); } - if (collectionType == CollectionType.TvShows) + if (collectionType == CollectionType.tvshows) { return ResolveVideos<Episode>(parent, files, false, collectionType, true); } @@ -403,7 +403,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies var multiDiscFolders = new List<FileSystemMetadata>(); var libraryOptions = args.LibraryOptions; - var supportPhotos = collectionType == CollectionType.HomeVideos && libraryOptions.EnablePhotos; + var supportPhotos = collectionType == CollectionType.homevideos && libraryOptions.EnablePhotos; var photos = new List<FileSystemMetadata>(); // Search for a folder rip @@ -459,7 +459,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ?? new MultiItemResolverResult(); - var isPhotosCollection = collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos; + var isPhotosCollection = collectionType == CollectionType.homevideos || collectionType == CollectionType.photos; if (!isPhotosCollection && result.Items.Count == 1) { var videoPath = result.Items[0].Path; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs index 29d540700..c0b00caaf 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -46,8 +46,8 @@ namespace Emby.Server.Implementations.Library.Resolvers // Must be an image file within a photo collection var collectionType = args.GetCollectionType(); - if (collectionType == CollectionType.Photos - || (collectionType == CollectionType.HomeVideos && args.LibraryOptions.EnablePhotos)) + if (collectionType == CollectionType.photos + || (collectionType == CollectionType.homevideos && args.LibraryOptions.EnablePhotos)) { if (HasPhotos(args)) { diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index d166ac37f..0934555b2 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -61,8 +61,8 @@ namespace Emby.Server.Implementations.Library.Resolvers // Must be an image file within a photo collection var collectionType = args.CollectionType; - if (collectionType == CollectionType.Photos - || (collectionType == CollectionType.HomeVideos && args.LibraryOptions.EnablePhotos)) + if (collectionType == CollectionType.photos + || (collectionType == CollectionType.homevideos && args.LibraryOptions.EnablePhotos)) { if (IsImageFile(args.Path, _imageProcessor)) { diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index d4b3722c9..a50435ae6 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.Library.Resolvers private CollectionType?[] _musicPlaylistCollectionTypes = { null, - CollectionType.Music + CollectionType.music }; /// <inheritdoc/> diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 8274881be..5fd23c9f5 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something // Also handle flat tv folders if (season is not null - || args.GetCollectionType() == CollectionType.TvShows + || args.GetCollectionType() == CollectionType.tvshows || args.HasParent<Series>()) { var episode = ResolveVideo<Episode>(args, false); diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 2ae1138a5..1484c34bc 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -60,11 +60,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var seriesInfo = Naming.TV.SeriesResolver.Resolve(_namingOptions, args.Path); var collectionType = args.GetCollectionType(); - if (collectionType == CollectionType.TvShows) + if (collectionType == CollectionType.tvshows) { // TODO refactor into separate class or something, this is copied from LibraryManager.GetConfiguredContentType var configuredContentType = args.GetConfiguredContentType(); - if (configuredContentType != CollectionType.TvShows) + if (configuredContentType != CollectionType.tvshows) { return new Series { diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index a0a90b129..8beeb8041 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -81,6 +81,53 @@ namespace Emby.Server.Implementations.Library }); } + public void SaveUserData(User user, BaseItem item, UpdateUserItemDataDto userDataDto, UserDataSaveReason reason) + { + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(item); + ArgumentNullException.ThrowIfNull(reason); + ArgumentNullException.ThrowIfNull(userDataDto); + + var userData = GetUserData(user, item); + + if (userDataDto.PlaybackPositionTicks.HasValue) + { + userData.PlaybackPositionTicks = userDataDto.PlaybackPositionTicks.Value; + } + + if (userDataDto.PlayCount.HasValue) + { + userData.PlayCount = userDataDto.PlayCount.Value; + } + + if (userDataDto.IsFavorite.HasValue) + { + userData.IsFavorite = userDataDto.IsFavorite.Value; + } + + if (userDataDto.Likes.HasValue) + { + userData.Likes = userDataDto.Likes.Value; + } + + if (userDataDto.Played.HasValue) + { + userData.Played = userDataDto.Played.Value; + } + + if (userDataDto.LastPlayedDate.HasValue) + { + userData.LastPlayedDate = userDataDto.LastPlayedDate.Value; + } + + if (userDataDto.Rating.HasValue) + { + userData.Rating = userDataDto.Rating.Value; + } + + SaveUserData(user, item, userData, reason, CancellationToken.None); + } + /// <summary> /// Save the provided user data for the given user. Batch operation. Does not fire any events or update the cache. /// </summary> diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 113370fc3..1d662ed8d 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library var folderViewType = collectionFolder?.CollectionType; // Playlist library requires special handling because the folder only references user playlists - if (folderViewType == CollectionType.Playlists) + if (folderViewType == CollectionType.playlists) { var items = folder.GetItemList(new InternalItemsQuery(user) { @@ -99,14 +99,14 @@ namespace Emby.Server.Implementations.Library } } - foreach (var viewType in new[] { CollectionType.Movies, CollectionType.TvShows }) + foreach (var viewType in new[] { CollectionType.movies, CollectionType.tvshows }) { var parents = groupedFolders.Where(i => i.CollectionType == viewType || i.CollectionType is null) .ToList(); if (parents.Count > 0) { - var localizationKey = viewType == CollectionType.TvShows + var localizationKey = viewType == CollectionType.tvshows ? "TvShows" : "Movies"; @@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.Library if (_config.Configuration.EnableFolderView) { var name = _localizationManager.GetLocalizedString("Folders"); - list.Add(_libraryManager.GetNamedView(name, CollectionType.Folders, string.Empty)); + list.Add(_libraryManager.GetNamedView(name, CollectionType.folders, string.Empty)); } if (query.IncludeExternalContent) @@ -279,7 +279,7 @@ namespace Emby.Server.Implementations.Library var isPlayed = request.IsPlayed; - if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.Music)) + if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.music)) { isPlayed = null; } @@ -305,11 +305,11 @@ namespace Emby.Server.Implementations.Library var hasCollectionType = parents.OfType<UserView>().ToArray(); if (hasCollectionType.Length > 0) { - if (hasCollectionType.All(i => i.CollectionType == CollectionType.Movies)) + if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies)) { includeItemTypes = new[] { BaseItemKind.Movie }; } - else if (hasCollectionType.All(i => i.CollectionType == CollectionType.TvShows)) + else if (hasCollectionType.All(i => i.CollectionType == CollectionType.tvshows)) { includeItemTypes = new[] { BaseItemKind.Episode }; } @@ -324,18 +324,18 @@ namespace Emby.Server.Implementations.Library { switch (parent.CollectionType) { - case CollectionType.Books: + case CollectionType.books: mediaTypes.Add(MediaType.Book); mediaTypes.Add(MediaType.Audio); break; - case CollectionType.Music: + case CollectionType.music: mediaTypes.Add(MediaType.Audio); break; - case CollectionType.Photos: + case CollectionType.photos: mediaTypes.Add(MediaType.Photo); mediaTypes.Add(MediaType.Video); break; - case CollectionType.HomeVideos: + case CollectionType.homevideos: mediaTypes.Add(MediaType.Photo); mediaTypes.Add(MediaType.Video); break; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index dd427c736..db06e4784 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -2168,7 +2168,7 @@ namespace Emby.Server.Implementations.LiveTv public Folder GetInternalLiveTvFolder(CancellationToken cancellationToken) { var name = _localization.GetLocalizedString("HeaderLiveTV"); - return _libraryManager.GetNamedView(name, CollectionType.LiveTv, name); + return _libraryManager.GetNamedView(name, CollectionType.livetv, name); } public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) diff --git a/Emby.Server.Implementations/Localization/Core/ab.json b/Emby.Server.Implementations/Localization/Core/ab.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ab.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json index 88a4a358e..55ee1abaa 100644 --- a/Emby.Server.Implementations/Localization/Core/fil.json +++ b/Emby.Server.Implementations/Localization/Core/fil.json @@ -124,5 +124,6 @@ "TaskKeyframeExtractor": "Tagabunot ng Keyframe", "TaskKeyframeExtractorDescription": "Nagbubunot ng keyframe mula sa mga bidyo upang makabuo ng mas tumpak na HLS playlist. Maaaring matagal ito gawin.", "External": "External", - "TaskRefreshTrickplayImages": "Gumawa ng Trickplay na Imahe" + "TaskRefreshTrickplayImages": "Gumawa ng Trickplay na Imahe", + "TaskRefreshTrickplayImagesDescription": "Nanggagawa ng mga trickplay prebiyu para sa mga bidyo sa pinaganang mga aklatan." } diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index 5c7dec7ef..0362c2417 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -124,5 +124,7 @@ "TaskKeyframeExtractorDescription": "Trekker ut nøkkelbilder fra videofiler for å skape mere nøyaktige HLS-spillelister. Denne oppgaven kan ta lang tid.", "TaskKeyframeExtractor": "Nøkkelbilde-uttrekker", "External": "Ekstern", - "HearingImpaired": "Hørselshemmet" + "HearingImpaired": "Hørselshemmet", + "TaskRefreshTrickplayImages": "Generer Trickplay bilder", + "TaskRefreshTrickplayImagesDescription": "Oppretter trickplay-forhåndsvisninger for videoer i aktiverte biblioteker." } diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index a4877f4b5..6a04115fa 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -89,7 +89,7 @@ "UserPolicyUpdatedWithName": "{0} için kullanıcı politikası güncellendi", "UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor", "UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi", - "ValueHasBeenAddedToLibrary": "Medya kütüphanenize {0} eklendi", + "ValueHasBeenAddedToLibrary": "{0} medya kütüphanenize eklendi", "ValueSpecialEpisodeName": "Özel - {0}", "VersionNumber": "Sürüm {0}", "TaskCleanCache": "Önbellek Dizinini Temizle", diff --git a/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs index 5c616d534..f65d609c7 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Playlists public override bool SupportsInheritedParentImages => false; [JsonIgnore] - public override CollectionType? CollectionType => Jellyfin.Data.Enums.CollectionType.Playlists; + public override CollectionType? CollectionType => Jellyfin.Data.Enums.CollectionType.playlists; protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) { diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 062e1062d..6cb1993e4 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -131,8 +131,8 @@ public class GenresController : BaseJellyfinApiController QueryResult<(BaseItem, ItemCounts)> result; if (parentItem is ICollectionFolder parentCollectionFolder - && (parentCollectionFolder.CollectionType == CollectionType.Music - || parentCollectionFolder.CollectionType == CollectionType.MusicVideos)) + && (parentCollectionFolder.CollectionType == CollectionType.music + || parentCollectionFolder.CollectionType == CollectionType.musicvideos)) { result = _libraryManager.GetMusicGenres(query); } diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 4e5ed60d5..9800248c6 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -171,7 +171,7 @@ public class ItemUpdateController : BaseJellyfinApiController info.ContentTypeOptions = GetContentTypeOptions(true).ToArray(); info.ContentType = configuredContentType; - if (inheritedContentType is null || inheritedContentType == CollectionType.TvShows) + if (inheritedContentType is null || inheritedContentType == CollectionType.tvshows) { info.ContentTypeOptions = info.ContentTypeOptions .Where(i => string.IsNullOrWhiteSpace(i.Value) diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 4e46e808a..a1fc8e11b 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -34,6 +34,7 @@ public class ItemsController : BaseJellyfinApiController private readonly IDtoService _dtoService; private readonly ILogger<ItemsController> _logger; private readonly ISessionManager _sessionManager; + private readonly IUserDataManager _userDataRepository; /// <summary> /// Initializes a new instance of the <see cref="ItemsController"/> class. @@ -44,13 +45,15 @@ public class ItemsController : BaseJellyfinApiController /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> + /// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param> public ItemsController( IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization, IDtoService dtoService, ILogger<ItemsController> logger, - ISessionManager sessionManager) + ISessionManager sessionManager, + IUserDataManager userDataRepository) { _userManager = userManager; _libraryManager = libraryManager; @@ -58,6 +61,7 @@ public class ItemsController : BaseJellyfinApiController _dtoService = dtoService; _logger = logger; _sessionManager = sessionManager; + _userDataRepository = userDataRepository; } /// <summary> @@ -275,7 +279,7 @@ public class ItemsController : BaseJellyfinApiController collectionType = hasCollectionType.CollectionType; } - if (collectionType == CollectionType.Playlists) + if (collectionType == CollectionType.playlists) { recursive = true; includeItemTypes = new[] { BaseItemKind.Playlist }; @@ -881,4 +885,64 @@ public class ItemsController : BaseJellyfinApiController itemsResult.TotalRecordCount, returnItems); } + + /// <summary> + /// Get Item User Data. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="itemId">The item id.</param> + /// <response code="200">return item user data.</response> + /// <response code="404">Item is not found.</response> + /// <returns>Return <see cref="UserItemDataDto"/>.</returns> + [HttpGet("Users/{userId}/Items/{itemId}/UserData")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<UserItemDataDto> GetItemUserData( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + { + if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true)) + { + return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to view this item user data."); + } + + var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException(); + var item = _libraryManager.GetItemById(itemId); + + return (item == null) ? NotFound() : _userDataRepository.GetUserDataDto(item, user); + } + + /// <summary> + /// Update Item User Data. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="itemId">The item id.</param> + /// <param name="userDataDto">New user data object.</param> + /// <response code="200">return updated user item data.</response> + /// <response code="404">Item is not found.</response> + /// <returns>Return <see cref="UserItemDataDto"/>.</returns> + [HttpPost("Users/{userId}/Items/{itemId}/UserData")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<UserItemDataDto> UpdateItemUserData( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId, + [FromBody, Required] UpdateUserItemDataDto userDataDto) + { + if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true)) + { + return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update this item user data."); + } + + var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException(); + var item = _libraryManager.GetItemById(itemId); + if (item == null) + { + return NotFound(); + } + + _userDataRepository.SaveUserData(user, item, userDataDto, UserDataSaveReason.UpdateUserData); + + return _userDataRepository.GetUserDataDto(item, user); + } } diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index af9a93719..de057bbab 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -927,15 +927,15 @@ public class LibraryController : BaseJellyfinApiController { return contentType switch { - CollectionType.BoxSets => new[] { "BoxSet" }, - CollectionType.Playlists => new[] { "Playlist" }, - CollectionType.Movies => new[] { "Movie" }, - CollectionType.TvShows => new[] { "Series", "Season", "Episode" }, - CollectionType.Books => new[] { "Book" }, - CollectionType.Music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" }, - CollectionType.HomeVideos => new[] { "Video", "Photo" }, - CollectionType.Photos => new[] { "Video", "Photo" }, - CollectionType.MusicVideos => new[] { "MusicVideo" }, + CollectionType.boxsets => new[] { "BoxSet" }, + CollectionType.playlists => new[] { "Playlist" }, + CollectionType.movies => new[] { "Movie" }, + CollectionType.tvshows => new[] { "Series", "Season", "Episode" }, + CollectionType.books => new[] { "Book" }, + CollectionType.music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" }, + CollectionType.homevideos => new[] { "Video", "Photo" }, + CollectionType.photos => new[] { "Video", "Photo" }, + CollectionType.musicvideos => new[] { "MusicVideo" }, _ => new[] { "Series", "Season", "Episode", "Movie" } }; } diff --git a/Jellyfin.Data/Enums/CollectionType.cs b/Jellyfin.Data/Enums/CollectionType.cs index e2044a0bc..e3d3b07af 100644 --- a/Jellyfin.Data/Enums/CollectionType.cs +++ b/Jellyfin.Data/Enums/CollectionType.cs @@ -1,3 +1,4 @@ +#pragma warning disable SA1300 // The name of a C# element does not begin with an upper-case letter. - disabled due to legacy requirement. using Jellyfin.Data.Attributes; namespace Jellyfin.Data.Enums; @@ -10,155 +11,155 @@ public enum CollectionType /// <summary> /// Unknown collection. /// </summary> - Unknown = 0, + unknown = 0, /// <summary> /// Movies collection. /// </summary> - Movies = 1, + movies = 1, /// <summary> /// Tv shows collection. /// </summary> - TvShows = 2, + tvshows = 2, /// <summary> /// Music collection. /// </summary> - Music = 3, + music = 3, /// <summary> /// Music videos collection. /// </summary> - MusicVideos = 4, + musicvideos = 4, /// <summary> /// Trailers collection. /// </summary> - Trailers = 5, + trailers = 5, /// <summary> /// Home videos collection. /// </summary> - HomeVideos = 6, + homevideos = 6, /// <summary> /// Box sets collection. /// </summary> - BoxSets = 7, + boxsets = 7, /// <summary> /// Books collection. /// </summary> - Books = 8, + books = 8, /// <summary> /// Photos collection. /// </summary> - Photos = 9, + photos = 9, /// <summary> /// Live tv collection. /// </summary> - LiveTv = 10, + livetv = 10, /// <summary> /// Playlists collection. /// </summary> - Playlists = 11, + playlists = 11, /// <summary> /// Folders collection. /// </summary> - Folders = 12, + folders = 12, /// <summary> /// Tv show series collection. /// </summary> [OpenApiIgnoreEnum] - TvShowSeries = 101, + tvshowseries = 101, /// <summary> /// Tv genres collection. /// </summary> [OpenApiIgnoreEnum] - TvGenres = 102, + tvgenres = 102, /// <summary> /// Tv genre collection. /// </summary> [OpenApiIgnoreEnum] - TvGenre = 103, + tvgenre = 103, /// <summary> /// Tv latest collection. /// </summary> [OpenApiIgnoreEnum] - TvLatest = 104, + tvlatest = 104, /// <summary> /// Tv next up collection. /// </summary> [OpenApiIgnoreEnum] - TvNextUp = 105, + tvnextup = 105, /// <summary> /// Tv resume collection. /// </summary> [OpenApiIgnoreEnum] - TvResume = 106, + tvresume = 106, /// <summary> /// Tv favorite series collection. /// </summary> [OpenApiIgnoreEnum] - TvFavoriteSeries = 107, + tvfavoriteseries = 107, /// <summary> /// Tv favorite episodes collection. /// </summary> [OpenApiIgnoreEnum] - TvFavoriteEpisodes = 108, + tvfavoriteepisodes = 108, /// <summary> /// Latest movies collection. /// </summary> [OpenApiIgnoreEnum] - MovieLatest = 109, + movielatest = 109, /// <summary> /// Movies to resume collection. /// </summary> [OpenApiIgnoreEnum] - MovieResume = 110, + movieresume = 110, /// <summary> /// Movie movie collection. /// </summary> [OpenApiIgnoreEnum] - MovieMovies = 111, + moviemovies = 111, /// <summary> /// Movie collections collection. /// </summary> [OpenApiIgnoreEnum] - MovieCollections = 112, + moviecollection = 112, /// <summary> /// Movie favorites collection. /// </summary> [OpenApiIgnoreEnum] - MovieFavorites = 113, + moviefavorites = 113, /// <summary> /// Movie genres collection. /// </summary> [OpenApiIgnoreEnum] - MovieGenres = 114, + moviegenres = 114, /// <summary> /// Movie genre collection. /// </summary> [OpenApiIgnoreEnum] - MovieGenre = 115 + moviegenre = 115 } diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index a4b4c1959..d8eee1246 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -110,21 +110,21 @@ namespace Jellyfin.Server.Implementations.Devices /// <inheritdoc /> public async Task<DeviceInfo?> GetDevice(string id) { - Device? device; var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); await using (dbContext.ConfigureAwait(false)) { - device = await dbContext.Devices + var device = await dbContext.Devices .Where(d => d.DeviceId == id) .OrderByDescending(d => d.DateLastActivity) .Include(d => d.User) + .SelectMany(d => dbContext.DeviceOptions.Where(o => o.DeviceId == d.DeviceId).DefaultIfEmpty(), (d, o) => new { Device = d, Options = o }) .FirstOrDefaultAsync() .ConfigureAwait(false); - } - var deviceInfo = device is null ? null : ToDeviceInfo(device); + var deviceInfo = device is null ? null : ToDeviceInfo(device.Device, device.Options); - return deviceInfo; + return deviceInfo; + } } /// <inheritdoc /> @@ -172,15 +172,15 @@ namespace Jellyfin.Server.Implementations.Devices var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); await using (dbContext.ConfigureAwait(false)) { - IAsyncEnumerable<Device> sessions = dbContext.Devices + var sessions = dbContext.Devices .Include(d => d.User) .OrderByDescending(d => d.DateLastActivity) .ThenBy(d => d.DeviceId) + .SelectMany(d => dbContext.DeviceOptions.Where(o => o.DeviceId == d.DeviceId).DefaultIfEmpty(), (d, o) => new { Device = d, Options = o }) .AsAsyncEnumerable(); - if (supportsSync.HasValue) { - sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == supportsSync.Value); + sessions = sessions.Where(i => GetCapabilities(i.Device.DeviceId).SupportsSync == supportsSync.Value); } if (userId.HasValue) @@ -191,10 +191,10 @@ namespace Jellyfin.Server.Implementations.Devices throw new ResourceNotFoundException(); } - sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)); + sessions = sessions.Where(i => CanAccessDevice(user, i.Device.DeviceId)); } - var array = await sessions.Select(device => ToDeviceInfo(device)).ToArrayAsync().ConfigureAwait(false); + var array = await sessions.Select(device => ToDeviceInfo(device.Device, device.Options)).ToArrayAsync().ConfigureAwait(false); return new QueryResult<DeviceInfo>(array); } @@ -226,7 +226,7 @@ namespace Jellyfin.Server.Implementations.Devices || !GetCapabilities(deviceId).SupportsPersistentIdentifier; } - private DeviceInfo ToDeviceInfo(Device authInfo) + private DeviceInfo ToDeviceInfo(Device authInfo, DeviceOptions? options = null) { var caps = GetCapabilities(authInfo.DeviceId); @@ -239,7 +239,8 @@ namespace Jellyfin.Server.Implementations.Devices LastUserName = authInfo.User.Username, Name = authInfo.DeviceName, DateLastActivity = authInfo.DateLastActivity, - IconUrl = caps.IconUrl + IconUrl = caps.IconUrl, + CustomName = options?.CustomName, }; } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 98485f9a8..7c04fcbfc 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -724,7 +724,7 @@ namespace MediaBrowser.Controller.Entities if (this is IHasCollectionType view) { - if (view.CollectionType == CollectionType.LiveTv) + if (view.CollectionType == CollectionType.livetv) { return true; } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 1f94cf767..eb026deb4 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -19,19 +19,19 @@ namespace MediaBrowser.Controller.Entities { private static readonly CollectionType?[] _viewTypesEligibleForGrouping = { - Jellyfin.Data.Enums.CollectionType.Movies, - Jellyfin.Data.Enums.CollectionType.TvShows, + Jellyfin.Data.Enums.CollectionType.movies, + Jellyfin.Data.Enums.CollectionType.tvshows, null }; private static readonly CollectionType?[] _originalFolderViewTypes = { - Jellyfin.Data.Enums.CollectionType.Books, - Jellyfin.Data.Enums.CollectionType.MusicVideos, - Jellyfin.Data.Enums.CollectionType.HomeVideos, - Jellyfin.Data.Enums.CollectionType.Photos, - Jellyfin.Data.Enums.CollectionType.Music, - Jellyfin.Data.Enums.CollectionType.BoxSets + Jellyfin.Data.Enums.CollectionType.books, + Jellyfin.Data.Enums.CollectionType.musicvideos, + Jellyfin.Data.Enums.CollectionType.homevideos, + Jellyfin.Data.Enums.CollectionType.photos, + Jellyfin.Data.Enums.CollectionType.music, + Jellyfin.Data.Enums.CollectionType.boxsets }; public static ITVSeriesManager TVSeriesManager { get; set; } @@ -161,7 +161,7 @@ namespace MediaBrowser.Controller.Entities return true; } - return collectionFolder.CollectionType == Jellyfin.Data.Enums.CollectionType.Playlists; + return collectionFolder.CollectionType == Jellyfin.Data.Enums.CollectionType.playlists; } public static bool IsEligibleForGrouping(Folder folder) diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 42431c832..a3525c862 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -58,58 +58,58 @@ namespace MediaBrowser.Controller.Entities switch (viewType) { - case CollectionType.Folders: + case CollectionType.folders: return GetResult(_libraryManager.GetUserRootFolder().GetChildren(user, true), query); - case CollectionType.TvShows: + case CollectionType.tvshows: return GetTvView(queryParent, user, query); - case CollectionType.Movies: + case CollectionType.movies: return GetMovieFolders(queryParent, user, query); - case CollectionType.TvShowSeries: + case CollectionType.tvshowseries: return GetTvSeries(queryParent, user, query); - case CollectionType.TvGenres: + case CollectionType.tvgenres: return GetTvGenres(queryParent, user, query); - case CollectionType.TvGenre: + case CollectionType.tvgenre: return GetTvGenreItems(queryParent, displayParent, user, query); - case CollectionType.TvResume: + case CollectionType.tvresume: return GetTvResume(queryParent, user, query); - case CollectionType.TvNextUp: + case CollectionType.tvnextup: return GetTvNextUp(queryParent, query); - case CollectionType.TvLatest: + case CollectionType.tvlatest: return GetTvLatest(queryParent, user, query); - case CollectionType.MovieFavorites: + case CollectionType.moviefavorites: return GetFavoriteMovies(queryParent, user, query); - case CollectionType.MovieLatest: + case CollectionType.movielatest: return GetMovieLatest(queryParent, user, query); - case CollectionType.MovieGenres: + case CollectionType.moviegenres: return GetMovieGenres(queryParent, user, query); - case CollectionType.MovieGenre: + case CollectionType.moviegenre: return GetMovieGenreItems(queryParent, displayParent, user, query); - case CollectionType.MovieResume: + case CollectionType.movieresume: return GetMovieResume(queryParent, user, query); - case CollectionType.MovieMovies: + case CollectionType.moviemovies: return GetMovieMovies(queryParent, user, query); - case CollectionType.MovieCollections: + case CollectionType.moviecollection: return GetMovieCollections(user, query); - case CollectionType.TvFavoriteEpisodes: + case CollectionType.tvfavoriteepisodes: return GetFavoriteEpisodes(queryParent, user, query); - case CollectionType.TvFavoriteSeries: + case CollectionType.tvfavoriteseries: return GetFavoriteSeries(queryParent, user, query); default: @@ -146,12 +146,12 @@ namespace MediaBrowser.Controller.Entities var list = new List<BaseItem> { - GetUserView(CollectionType.MovieResume, "HeaderContinueWatching", "0", parent), - GetUserView(CollectionType.MovieLatest, "Latest", "1", parent), - GetUserView(CollectionType.MovieMovies, "Movies", "2", parent), - GetUserView(CollectionType.MovieCollections, "Collections", "3", parent), - GetUserView(CollectionType.MovieFavorites, "Favorites", "4", parent), - GetUserView(CollectionType.MovieGenres, "Genres", "5", parent) + GetUserView(CollectionType.movieresume, "HeaderContinueWatching", "0", parent), + GetUserView(CollectionType.movielatest, "Latest", "1", parent), + GetUserView(CollectionType.moviemovies, "Movies", "2", parent), + GetUserView(CollectionType.moviecollection, "Collections", "3", parent), + GetUserView(CollectionType.moviefavorites, "Favorites", "4", parent), + GetUserView(CollectionType.moviegenres, "Genres", "5", parent) }; return GetResult(list, query); @@ -264,7 +264,7 @@ namespace MediaBrowser.Controller.Entities } }) .Where(i => i is not null) - .Select(i => GetUserViewWithName(CollectionType.MovieGenre, i.SortName, parent)); + .Select(i => GetUserViewWithName(CollectionType.moviegenre, i.SortName, parent)); return GetResult(genres, query); } @@ -303,13 +303,13 @@ namespace MediaBrowser.Controller.Entities var list = new List<BaseItem> { - GetUserView(CollectionType.TvResume, "HeaderContinueWatching", "0", parent), - GetUserView(CollectionType.TvNextUp, "HeaderNextUp", "1", parent), - GetUserView(CollectionType.TvLatest, "Latest", "2", parent), - GetUserView(CollectionType.TvShowSeries, "Shows", "3", parent), - GetUserView(CollectionType.TvFavoriteSeries, "HeaderFavoriteShows", "4", parent), - GetUserView(CollectionType.TvFavoriteEpisodes, "HeaderFavoriteEpisodes", "5", parent), - GetUserView(CollectionType.TvGenres, "Genres", "6", parent) + GetUserView(CollectionType.tvresume, "HeaderContinueWatching", "0", parent), + GetUserView(CollectionType.tvnextup, "HeaderNextUp", "1", parent), + GetUserView(CollectionType.tvlatest, "Latest", "2", parent), + GetUserView(CollectionType.tvshowseries, "Shows", "3", parent), + GetUserView(CollectionType.tvfavoriteseries, "HeaderFavoriteShows", "4", parent), + GetUserView(CollectionType.tvfavoriteepisodes, "HeaderFavoriteEpisodes", "5", parent), + GetUserView(CollectionType.tvgenres, "Genres", "6", parent) }; return GetResult(list, query); @@ -330,7 +330,7 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetTvNextUp(Folder parent, InternalItemsQuery query) { - var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.TvShows }); + var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.tvshows }); var result = _tvSeriesManager.GetNextUp( new NextUpQuery @@ -392,7 +392,7 @@ namespace MediaBrowser.Controller.Entities } }) .Where(i => i is not null) - .Select(i => GetUserViewWithName(CollectionType.TvGenre, i.SortName, parent)); + .Select(i => GetUserViewWithName(CollectionType.tvgenre, i.SortName, parent)); return GetResult(genres, query); } diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index 034c40591..43cccfc65 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -35,6 +35,15 @@ namespace MediaBrowser.Controller.Library void SaveUserData(User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); + /// <summary> + /// Save the provided user data for the given user. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="item">The item.</param> + /// <param name="userDataDto">The reason for updating the user data.</param> + /// <param name="reason">The reason.</param> + void SaveUserData(User user, BaseItem item, UpdateUserItemDataDto userDataDto, UserDataSaveReason reason); + UserItemData GetUserData(User user, BaseItem item); UserItemData GetUserData(Guid userId, BaseItem item); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 46fd1ae47..6a16d421c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3343,7 +3343,7 @@ namespace MediaBrowser.Controller.MediaEncoding // [0:s]scale=s=1280x720 var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subSwScaleFilter); - overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); + overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } return (mainFilters, subFilters, overlayFilters); @@ -3520,7 +3520,7 @@ namespace MediaBrowser.Controller.MediaEncoding } subFilters.Add("hwupload=derive_device=cuda"); - overlayFilters.Add("overlay_cuda=eof_action=endall:shortest=1:repeatlast=0"); + overlayFilters.Add("overlay_cuda=eof_action=pass:repeatlast=0"); } } else @@ -3529,7 +3529,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subSwScaleFilter); - overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); + overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -3718,7 +3718,7 @@ namespace MediaBrowser.Controller.MediaEncoding } subFilters.Add("hwupload=derive_device=opencl"); - overlayFilters.Add("overlay_opencl=eof_action=endall:shortest=1:repeatlast=0"); + overlayFilters.Add("overlay_opencl=eof_action=pass:repeatlast=0"); overlayFilters.Add("hwmap=derive_device=d3d11va:reverse=1"); overlayFilters.Add("format=d3d11"); } @@ -3729,7 +3729,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subSwScaleFilter); - overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); + overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -3964,7 +3964,7 @@ namespace MediaBrowser.Controller.MediaEncoding : string.Empty; var overlayQsvFilter = string.Format( CultureInfo.InvariantCulture, - "overlay_qsv=eof_action=endall:shortest=1:repeatlast=0{0}", + "overlay_qsv=eof_action=pass:repeatlast=0{0}", overlaySize); overlayFilters.Add(overlayQsvFilter); } @@ -3975,7 +3975,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subSwScaleFilter); - overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); + overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -4180,7 +4180,7 @@ namespace MediaBrowser.Controller.MediaEncoding : string.Empty; var overlayQsvFilter = string.Format( CultureInfo.InvariantCulture, - "overlay_qsv=eof_action=endall:shortest=1:repeatlast=0{0}", + "overlay_qsv=eof_action=pass:repeatlast=0{0}", overlaySize); overlayFilters.Add(overlayQsvFilter); } @@ -4191,7 +4191,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subSwScaleFilter); - overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); + overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } } @@ -4445,7 +4445,7 @@ namespace MediaBrowser.Controller.MediaEncoding : string.Empty; var overlayVaapiFilter = string.Format( CultureInfo.InvariantCulture, - "overlay_vaapi=eof_action=endall:shortest=1:repeatlast=0{0}", + "overlay_vaapi=eof_action=pass:repeatlast=0{0}", overlaySize); overlayFilters.Add(overlayVaapiFilter); } @@ -4456,7 +4456,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subSwScaleFilter); - overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); + overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); if (isVaapiEncoder) { @@ -4616,7 +4616,7 @@ namespace MediaBrowser.Controller.MediaEncoding subFilters.Add("hwupload=derive_device=vulkan"); subFilters.Add("format=vulkan"); - overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0"); + overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0"); if (isSwEncoder) { @@ -4817,7 +4817,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subSwScaleFilter); - overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); + overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); if (isVaapiEncoder) { diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs index 7a1c7a738..4962992a0 100644 --- a/MediaBrowser.Model/Devices/DeviceInfo.cs +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -15,6 +15,8 @@ namespace MediaBrowser.Model.Devices public string Name { get; set; } + public string CustomName { get; set; } + /// <summary> /// Gets or sets the access token. /// </summary> diff --git a/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs b/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs new file mode 100644 index 000000000..7bfedf973 --- /dev/null +++ b/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs @@ -0,0 +1,76 @@ +using System; + +namespace MediaBrowser.Model.Dto +{ + /// <summary> + /// This is used by the api to get information about a item user data. + /// </summary> + public class UpdateUserItemDataDto + { + /// <summary> + /// Gets or sets the rating. + /// </summary> + /// <value>The rating.</value> + public double? Rating { get; set; } + + /// <summary> + /// Gets or sets the played percentage. + /// </summary> + /// <value>The played percentage.</value> + public double? PlayedPercentage { get; set; } + + /// <summary> + /// Gets or sets the unplayed item count. + /// </summary> + /// <value>The unplayed item count.</value> + public int? UnplayedItemCount { get; set; } + + /// <summary> + /// Gets or sets the playback position ticks. + /// </summary> + /// <value>The playback position ticks.</value> + public long? PlaybackPositionTicks { get; set; } + + /// <summary> + /// Gets or sets the play count. + /// </summary> + /// <value>The play count.</value> + public int? PlayCount { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is favorite. + /// </summary> + /// <value><c>true</c> if this instance is favorite; otherwise, <c>false</c>.</value> + public bool? IsFavorite { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this <see cref="UpdateUserItemDataDto" /> is likes. + /// </summary> + /// <value><c>null</c> if [likes] contains no value, <c>true</c> if [likes]; otherwise, <c>false</c>.</value> + public bool? Likes { get; set; } + + /// <summary> + /// Gets or sets the last played date. + /// </summary> + /// <value>The last played date.</value> + public DateTime? LastPlayedDate { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this <see cref="UserItemDataDto" /> is played. + /// </summary> + /// <value><c>true</c> if played; otherwise, <c>false</c>.</value> + public bool? Played { get; set; } + + /// <summary> + /// Gets or sets the key. + /// </summary> + /// <value>The key.</value> + public string? Key { get; set; } + + /// <summary> + /// Gets or sets the item identifier. + /// </summary> + /// <value>The item identifier.</value> + public string? ItemId { get; set; } + } +} diff --git a/MediaBrowser.Model/Entities/UserDataSaveReason.cs b/MediaBrowser.Model/Entities/UserDataSaveReason.cs index 20404e6f4..b8e73a98c 100644 --- a/MediaBrowser.Model/Entities/UserDataSaveReason.cs +++ b/MediaBrowser.Model/Entities/UserDataSaveReason.cs @@ -33,6 +33,11 @@ namespace MediaBrowser.Model.Entities /// <summary> /// The import. /// </summary> - Import = 6 + Import = 6, + + /// <summary> + /// API call updated item user data. + /// </summary> + UpdateUserData = 7, } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs index 16202aea9..5aa7c04f6 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs @@ -63,7 +63,7 @@ public class AudioResolverTests null, Mock.Of<ILibraryManager>()) { - CollectionType = CollectionType.Books, + CollectionType = CollectionType.books, FileInfo = new FileSystemMetadata { FullName = parent, diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs index 92bac722b..cc2e47c33 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs @@ -29,7 +29,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library null) { Parent = parent, - CollectionType = CollectionType.TvShows, + CollectionType = CollectionType.tvshows, FileInfo = new FileSystemMetadata { FullName = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv" @@ -52,7 +52,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library null) { Parent = series, - CollectionType = CollectionType.TvShows, + CollectionType = CollectionType.tvshows, FileInfo = new FileSystemMetadata { FullName = "Extras/Extras S01E01.mkv" |
