diff options
60 files changed, 395 insertions, 174 deletions
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml index f43d743f0..f43d743f0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/ci-codeql-analysis.yml diff --git a/.github/workflows/openapi.yml b/.github/workflows/ci-openapi.yml index 8c463a8fc..8c463a8fc 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/ci-openapi.yml diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml new file mode 100644 index 000000000..36686e64b --- /dev/null +++ b/.github/workflows/ci-tests.yml @@ -0,0 +1,50 @@ +name: Tests +on: + push: + branches: + - master + # Run tests against the forked branch, but + # do not allow access to secrets + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflows-in-forked-repositories + pull_request: + +env: + SDK_VERSION: "7.0.x" + +jobs: + run-tests: + strategy: + matrix: + os: ["ubuntu-latest", "macos-latest", "windows-latest"] + + runs-on: "${{ matrix.os }}" + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3 + with: + dotnet-version: ${{ env.SDK_VERSION }} + + - name: Run DotNet CLI Tests + run: > + dotnet test Jellyfin.sln + --configuration Release + --collect:"XPlat Code Coverage" + --settings tests/coverletArgs.runsettings + --verbosity minimal + + - name: Merge code coverage results + uses: danielpalme/ReportGenerator-GitHub-Action@873ee34c88a6234bdab7fd264d3666fd1ab417f7 # 5 + with: + reports: "**/coverage.cobertura.xml" + targetdir: "merged/" + reporttypes: "Cobertura" + + # 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/.github/workflows/repo-stale.yaml b/.github/workflows/issue-stale.yml index f9075ba03..926a7fbfb 100644 --- a/.github/workflows/repo-stale.yaml +++ b/.github/workflows/issue-stale.yml @@ -1,4 +1,4 @@ -name: Stale Check +name: Stale Issue Labeler on: schedule: @@ -28,27 +28,8 @@ jobs: exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed stale-issue-label: stale stale-issue-message: |- - This issue has gone 120 days without an update and will be closed within 21 days if there is no new activity. To prevent this issue from being closed, please confirm the issue has not already been fixed by providing updated examples or logs. + This issue has gone 120 days without an update and will be closed within 21 days if there is no new activity. To prevent this issue from being closed, please confirm the issue has not already been fixed by providing updated examples or logs. If you have any questions you can use one of several ways to [contact us](https://jellyfin.org/contact). close-issue-message: |- This issue was closed due to inactivity. - - prs-conflicts: - name: Check PRs with merge conflicts - runs-on: ubuntu-latest - if: ${{ contains(github.repository, 'jellyfin/') }} - steps: - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 - with: - repo-token: ${{ secrets.JF_BOT_TOKEN }} - ascending: true - operations-per-run: 150 - # The merge conflict action will remove the label when updated - remove-stale-when-updated: false - days-before-stale: -1 - days-before-close: 90 - days-before-issue-close: -1 - stale-pr-label: merge conflict - close-pr-message: |- - This PR has been closed due to having unresolved merge conflicts. diff --git a/.github/workflows/automation.yml b/.github/workflows/project-automation.yml index 47abce02a..3637eb16a 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/project-automation.yml @@ -1,4 +1,4 @@ -name: Automation +name: Project Automation on: push: @@ -9,19 +9,6 @@ on: permissions: {} jobs: - label: - name: Labeling - runs-on: ubuntu-latest - if: ${{ github.repository == 'jellyfin/jellyfin' }} - steps: - - name: Apply label - uses: eps1lon/actions-label-merge-conflict@fd1f295ee7443d13745804bc49fe158e240f6c6e # tag=v2.1.0 - if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}} - with: - dirtyLabel: 'merge conflict' - commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.' - repoToken: ${{ secrets.JF_BOT_TOKEN }} - project: name: Project board runs-on: ubuntu-latest diff --git a/.github/workflows/pull-request-conflict.yml b/.github/workflows/pull-request-conflict.yml new file mode 100644 index 000000000..05517bb03 --- /dev/null +++ b/.github/workflows/pull-request-conflict.yml @@ -0,0 +1,23 @@ +name: Merge Conflict Labeler + +on: + push: + branches: + - master + pull_request_target: + issue_comment: + +permissions: {} +jobs: + label: + name: Labeling + runs-on: ubuntu-latest + if: ${{ github.repository == 'jellyfin/jellyfin' }} + steps: + - name: Apply label + uses: eps1lon/actions-label-merge-conflict@fd1f295ee7443d13745804bc49fe158e240f6c6e # tag=v2.1.0 + if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}} + with: + dirtyLabel: 'merge conflict' + commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.' + repoToken: ${{ secrets.JF_BOT_TOKEN }} diff --git a/.github/workflows/pull-request-stale.yaml b/.github/workflows/pull-request-stale.yaml new file mode 100644 index 000000000..de093a988 --- /dev/null +++ b/.github/workflows/pull-request-stale.yaml @@ -0,0 +1,30 @@ +name: Stale PR Check + +on: + schedule: + - cron: '30 */12 * * *' + workflow_dispatch: + +permissions: + pull-requests: write + actions: write + +jobs: + prs-stale-conflicts: + name: Check PRs with merge conflicts + runs-on: ubuntu-latest + if: ${{ contains(github.repository, 'jellyfin/') }} + steps: + - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 + with: + repo-token: ${{ secrets.JF_BOT_TOKEN }} + ascending: true + operations-per-run: 150 + # The merge conflict action will remove the label when updated + remove-stale-when-updated: false + days-before-stale: -1 + days-before-close: 90 + days-before-issue-close: -1 + stale-pr-label: merge conflict + close-pr-message: |- + This PR has been closed due to having unresolved merge conflicts. diff --git a/.github/workflows/repo-bump-version.yaml b/.github/workflows/release-bump-version.yaml index e0383afd2..e0383afd2 100644 --- a/.github/workflows/repo-bump-version.yaml +++ b/.github/workflows/release-bump-version.yaml diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 74f1a8965..fff7136b8 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -171,6 +171,7 @@ - [tallbl0nde](https://github.com/tallbl0nde) - [sleepycatcoding](https://github.com/sleepycatcoding) - [scampower3](https://github.com/scampower3) + - [Chris-Codes-It] (https://github.com/Chris-Codes-It) # Emby Contributors @@ -241,4 +242,4 @@ - [Jakob Kukla](https://github.com/jakobkukla) - [Utku Özdemir](https://github.com/utkuozdemir) - [JPUC1143](https://github.com/Jpuc1143/) - - [0x25CBFC4F](https://github.com/0x25CBFC4F) + - [0x25CBFC4F](https://github.com/0x25CBFC4F)
\ No newline at end of file diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 7d53263a6..99068826d 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -494,7 +494,7 @@ namespace Emby.Dlna.ContentDirectory { var folder = (Folder)item; - string[] mediaTypes = Array.Empty<string>(); + MediaType[] mediaTypes = Array.Empty<MediaType>(); bool? isFolder = null; switch (search.SearchType) diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 5ed982876..9f152df13 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -174,13 +174,14 @@ namespace Emby.Dlna.Didl if (item is IHasMediaSources) { - if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + switch (item.MediaType) { - AddAudioResource(writer, item, deviceId, filter, streamInfo); - } - else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) - { - AddVideoResource(writer, item, deviceId, filter, streamInfo); + case MediaType.Audio: + AddAudioResource(writer, item, deviceId, filter, streamInfo); + break; + case MediaType.Video: + AddVideoResource(writer, item, deviceId, filter, streamInfo); + break; } } @@ -821,15 +822,15 @@ namespace Emby.Dlna.Didl writer.WriteString(classType ?? "object.container.storageFolder"); } - else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + else if (item.MediaType == MediaType.Audio) { writer.WriteString("object.item.audioItem.musicTrack"); } - else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) + else if (item.MediaType == MediaType.Photo) { writer.WriteString("object.item.imageItem.photo"); } - else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + else if (item.MediaType == MediaType.Video) { if (!_profile.RequiresPlainVideoItems && item is Movie) { @@ -1006,8 +1007,7 @@ namespace Emby.Dlna.Didl if (!_profile.EnableAlbumArtInDidl) { - if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) - || string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video) { if (!stubType.HasValue) { @@ -1016,7 +1016,7 @@ namespace Emby.Dlna.Didl } } - if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) + if (!_profile.EnableSingleAlbumArtLimit || item.MediaType == MediaType.Photo) { AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG"); AddImageResElement(item, writer, 1024, 768, "jpg", "JPEG_MED"); diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index df02fe631..f70ebf3eb 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Dlna.Didl; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; @@ -577,7 +578,7 @@ namespace Emby.Dlna.PlayTo private PlaylistItem GetPlaylistItem(BaseItem item, MediaSourceInfo[] mediaSources, DeviceProfile profile, string deviceId, string? mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex) { - if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + if (item.MediaType == MediaType.Video) { return new PlaylistItem { @@ -597,7 +598,7 @@ namespace Emby.Dlna.PlayTo }; } - if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + if (item.MediaType == MediaType.Audio) { return new PlaylistItem { @@ -615,7 +616,7 @@ namespace Emby.Dlna.PlayTo }; } - if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) + if (item.MediaType == MediaType.Photo) { return PlaylistItemFactory.Create((Photo)item, profile); } diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs index 2e0f2063b..a8f451405 100644 --- a/Emby.Dlna/PlayTo/uBaseObject.cs +++ b/Emby.Dlna/PlayTo/uBaseObject.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Enums; namespace Emby.Dlna.PlayTo { @@ -33,19 +34,19 @@ namespace Emby.Dlna.PlayTo { var classType = UpnpClass ?? string.Empty; - if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Audio, StringComparison.Ordinal) != -1) + if (classType.Contains("Audio", StringComparison.Ordinal)) { - return MediaBrowser.Model.Entities.MediaType.Audio; + return "Audio"; } - if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1) + if (classType.Contains("Video", StringComparison.Ordinal)) { - return MediaBrowser.Model.Entities.MediaType.Video; + return "Video"; } - if (classType.IndexOf("image", StringComparison.Ordinal) != -1) + if (classType.Contains("image", StringComparison.Ordinal)) { - return MediaBrowser.Model.Entities.MediaType.Photo; + return "Photo"; } return null; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index fadd4f2f3..d0772654c 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -722,7 +722,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@IsLocked", item.IsLocked); saveItemStatement.TryBind("@Name", item.Name); saveItemStatement.TryBind("@OfficialRating", item.OfficialRating); - saveItemStatement.TryBind("@MediaType", item.MediaType); + saveItemStatement.TryBind("@MediaType", item.MediaType.ToString()); saveItemStatement.TryBind("@Overview", item.Overview); saveItemStatement.TryBind("@ParentIndexNumber", item.ParentIndexNumber); saveItemStatement.TryBind("@PremiereDate", item.PremiereDate); @@ -2986,11 +2986,6 @@ namespace Emby.Server.Implementations.Data return true; } - private bool IsValidMediaType(string value) - { - return IsAlphaNumeric(value); - } - private bool IsValidPersonType(string value) { return IsAlphaNumeric(value); @@ -3998,15 +3993,14 @@ namespace Emby.Server.Implementations.Data } } - var queryMediaTypes = query.MediaTypes.Where(IsValidMediaType).ToArray(); - if (queryMediaTypes.Length == 1) + if (query.MediaTypes.Length == 1) { whereClauses.Add("MediaType=@MediaTypes"); - statement?.TryBind("@MediaTypes", queryMediaTypes[0]); + statement?.TryBind("@MediaTypes", query.MediaTypes[0].ToString()); } - else if (queryMediaTypes.Length > 1) + else if (query.MediaTypes.Length > 1) { - var val = string.Join(',', queryMediaTypes.Select(i => "'" + i + "'")); + var val = string.Join(',', query.MediaTypes.Select(i => $"'{i}'")); whereClauses.Add("MediaType in (" + val + ")"); } diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 91469dba9..96fad9bca 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using EasyCaching.Core.Configurations; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Extensions.Json; @@ -186,11 +187,11 @@ namespace Emby.Server.Implementations.Library { SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); - if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + if (item.MediaType == MediaType.Audio) { source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); } - else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + else if (item.MediaType == MediaType.Video) { source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding); source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing); @@ -334,11 +335,11 @@ namespace Emby.Server.Implementations.Library { SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); - if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + if (item.MediaType == MediaType.Audio) { source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); } - else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + else if (item.MediaType == MediaType.Video) { source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding); source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing); @@ -417,9 +418,9 @@ namespace Emby.Server.Implementations.Library public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) { // Item would only be null if the app didn't supply ItemId as part of the live stream open request - var mediaType = item is null ? MediaType.Video : item.MediaType; + var mediaType = item?.MediaType ?? MediaType.Video; - if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + if (mediaType == MediaType.Video) { var userData = item is null ? new UserItemData() : _userDataManager.GetUserData(user, item); @@ -428,7 +429,7 @@ namespace Emby.Server.Implementations.Library SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection); SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection); } - else if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + else if (mediaType == MediaType.Audio) { var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index df5610996..113370fc3 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -316,7 +316,7 @@ namespace Emby.Server.Implementations.Library } } - var mediaTypes = new List<string>(); + var mediaTypes = new List<MediaType>(); if (includeItemTypes.Length == 0) { diff --git a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs index df45793c3..89f64ee4f 100644 --- a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs @@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators { var movies = _libraryManager.GetItemList(new InternalItemsQuery { - MediaTypes = new string[] { MediaType.Video }, + MediaTypes = new[] { MediaType.Video }, IncludeItemTypes = new[] { BaseItemKind.Movie }, IsVirtualItem = false, OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json index 3ee045d89..b816738c2 100644 --- a/Emby.Server.Implementations/Localization/Core/fr-CA.json +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -124,5 +124,7 @@ "TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.", "TaskKeyframeExtractor": "Extracteur d'image clé", "External": "Externe", - "HearingImpaired": "Malentendants" + "HearingImpaired": "Malentendants", + "TaskRefreshTrickplayImages": "Générer des images Trickplay", + "TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées." } diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index dcfe46efc..92ac2681e 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -104,8 +104,8 @@ "TaskRefreshPeople": "Atualizar Pessoas", "TaskCleanLogsDescription": "Apagar ficheiros de log que têm mais de {0} dias.", "TaskCleanLogs": "Limpar a Diretoria de Logs", - "TaskRefreshLibraryDescription": "Scannear a biblioteca de música para novos ficheiros e atualizar os metadados.", - "TaskRefreshLibrary": "Scannear Biblioteca de Música", + "TaskRefreshLibraryDescription": "Analisar a biblioteca de música para novos ficheiros e atualizar os metadados.", + "TaskRefreshLibrary": "Analisar Biblioteca de Música", "TaskRefreshChapterImagesDescription": "Criar thumbnails para os vídeos que têm capítulos.", "TaskRefreshChapterImages": "Extrair Imagens dos Capítulos", "TaskCleanCacheDescription": "Apagar ficheiros em cache que já não são necessários.", diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 649c49924..d2e2fd7d5 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -74,7 +75,7 @@ namespace Emby.Server.Implementations.Playlists throw new ArgumentException(nameof(parentFolder)); } - if (string.IsNullOrEmpty(options.MediaType)) + if (options.MediaType is null || options.MediaType == MediaType.Unknown) { foreach (var itemId in options.ItemIdList) { @@ -84,7 +85,7 @@ namespace Emby.Server.Implementations.Playlists throw new ArgumentException("No item exists with the supplied Id"); } - if (!string.IsNullOrEmpty(item.MediaType)) + if (item.MediaType != MediaType.Unknown) { options.MediaType = item.MediaType; } @@ -102,20 +103,20 @@ namespace Emby.Server.Implementations.Playlists { options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist) .Select(i => i.MediaType) - .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + .FirstOrDefault(i => i != MediaType.Unknown); } } - if (!string.IsNullOrEmpty(options.MediaType)) + if (options.MediaType is not null && options.MediaType != MediaType.Unknown) { break; } } } - if (string.IsNullOrEmpty(options.MediaType)) + if (options.MediaType is null || options.MediaType == MediaType.Unknown) { - options.MediaType = "Audio"; + options.MediaType = MediaType.Audio; } var user = _userManager.GetUserById(options.UserId); @@ -168,7 +169,7 @@ namespace Emby.Server.Implementations.Playlists return path; } - private List<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, string playlistMediaType, User user, DtoOptions options) + private List<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, MediaType playlistMediaType, User user, DtoOptions options) { var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i is not null); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 5d15c3a21..d03d40863 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index c3c43982b..e7d3e694a 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -95,7 +95,7 @@ public class ArtistsController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, @@ -299,7 +299,7 @@ public class ArtistsController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index d51a5325f..baeb8b81a 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -50,7 +50,7 @@ public class FilterController : BaseJellyfinApiController [FromQuery] Guid? userId, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.Value.Equals(default) diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 3920d6599..4e46e808a 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -195,7 +195,7 @@ public class ItemsController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, [FromQuery] bool? isPlayed, @@ -652,7 +652,7 @@ public class ItemsController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, [FromQuery] bool? isPlayed, @@ -812,7 +812,7 @@ public class ItemsController : BaseJellyfinApiController [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index 8d2a738d4..c4c89ccde 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.PlaylistDtos; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; @@ -75,7 +76,7 @@ public class PlaylistsController : BaseJellyfinApiController [FromQuery, ParameterObsolete] string? name, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids, [FromQuery, ParameterObsolete] Guid? userId, - [FromQuery, ParameterObsolete] string? mediaType, + [FromQuery, ParameterObsolete] MediaType? mediaType, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] CreatePlaylistDto? createPlaylistRequest) { if (ids.Count == 0) diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 387b3ea5a..5b4594165 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -86,7 +86,7 @@ public class SearchController : BaseJellyfinApiController [FromQuery, Required] string searchTerm, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery] Guid? parentId, [FromQuery] bool? isMovie, [FromQuery] bool? isSeries, diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index e93456de6..e20cf034d 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -393,7 +393,7 @@ public class SessionController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<ActionResult> PostCapabilities( [FromQuery] string? id, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] playableMediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] playableMediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands, [FromQuery] bool supportsMediaControl = false, [FromQuery] bool supportsSync = false, diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index 5b808f257..675757fc5 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -56,7 +56,7 @@ public class SuggestionsController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult<QueryResult<BaseItemDto>> GetSuggestions( [FromRoute, Required] Guid userId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaType, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaType, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] type, [FromQuery] int? startIndex, [FromQuery] int? limit, diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 1246efd49..4fbaafa2a 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -160,7 +160,7 @@ public class TrailersController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, [FromQuery] bool? isPlayed, diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 641cdd81e..ca46c38c5 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -76,7 +76,7 @@ public class YearsController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -191,7 +191,7 @@ public class YearsController : BaseJellyfinApiController return _dtoService.GetBaseItemDto(item, dtoOptions); } - private bool FilterItem(BaseItem f, IReadOnlyCollection<BaseItemKind> excludeItemTypes, IReadOnlyCollection<BaseItemKind> includeItemTypes, IReadOnlyCollection<string> mediaTypes) + private bool FilterItem(BaseItem f, IReadOnlyCollection<BaseItemKind> excludeItemTypes, IReadOnlyCollection<BaseItemKind> includeItemTypes, IReadOnlyCollection<MediaType> mediaTypes) { var baseItemKind = f.GetBaseItemKind(); // Exclude item types @@ -207,7 +207,7 @@ public class YearsController : BaseJellyfinApiController } // Include MediaTypes - if (mediaTypes.Count > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (mediaTypes.Count > 0 && !mediaTypes.Contains(f.MediaType)) { return false; } diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index a36028cfe..321987ca7 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -243,7 +243,7 @@ public class MediaInfoHelper } // Beginning of Playback Determination - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) + var streamInfo = item.MediaType == MediaType.Audio ? streamBuilder.GetOptimalAudioStream(options) : streamBuilder.GetOptimalVideoStream(options); diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 11f6bcf6b..6fbbceeab 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -128,7 +129,7 @@ public static class StreamingHelpers var item = libraryManager.GetItemById(streamingRequest.Id); - state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); + state.IsInputVideo = item.MediaType == MediaType.Video; MediaSourceInfo? mediaSource = null; if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)) diff --git a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs index 1fba32c5b..bdc488871 100644 --- a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs +++ b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using Jellyfin.Extensions.Json.Converters; namespace Jellyfin.Api.Models.PlaylistDtos; @@ -29,5 +30,5 @@ public class CreatePlaylistDto /// <summary> /// Gets or sets the media type. /// </summary> - public string? MediaType { get; set; } + public MediaType? MediaType { get; set; } } diff --git a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs b/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs index b88be33b2..b021771a0 100644 --- a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs +++ b/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Session; @@ -16,7 +17,7 @@ public class ClientCapabilitiesDto /// Gets or sets the list of playable media types. /// </summary> [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] - public IReadOnlyList<string> PlayableMediaTypes { get; set; } = Array.Empty<string>(); + public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = Array.Empty<MediaType>(); /// <summary> /// Gets or sets the list of supported commands. diff --git a/Jellyfin.Data/Enums/MediaType.cs b/Jellyfin.Data/Enums/MediaType.cs new file mode 100644 index 000000000..b014ff366 --- /dev/null +++ b/Jellyfin.Data/Enums/MediaType.cs @@ -0,0 +1,32 @@ +namespace Jellyfin.Data.Enums; + +/// <summary> +/// Media types. +/// </summary> +public enum MediaType +{ + /// <summary> + /// Unknown media type. + /// </summary> + Unknown = 0, + + /// <summary> + /// Video media. + /// </summary> + Video = 1, + + /// <summary> + /// Audio media. + /// </summary> + Audio = 2, + + /// <summary> + /// Photo media. + /// </summary> + Photo = 3, + + /// <summary> + /// Book media. + /// </summary> + Book = 4 +} diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs index 27726a57a..8a33383e3 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Activity; @@ -89,14 +90,14 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session return name; } - private static string GetPlaybackNotificationType(string mediaType) + private static string GetPlaybackNotificationType(MediaType mediaType) { - if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + if (mediaType == MediaType.Audio) { return NotificationType.AudioPlayback.ToString(); } - if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + if (mediaType == MediaType.Video) { return NotificationType.VideoPlayback.ToString(); } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs index 6b16477aa..4c2effc2e 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Activity; @@ -97,14 +98,14 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session return name; } - private static string? GetPlaybackStoppedNotificationType(string mediaType) + private static string? GetPlaybackStoppedNotificationType(MediaType mediaType) { - if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + if (mediaType == MediaType.Audio) { return NotificationType.AudioPlaybackStopped.ToString(); } - if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + if (mediaType == MediaType.Video) { return NotificationType.VideoPlaybackStopped.ToString(); } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index c7216a320..243d2f04f 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// </summary> /// <value>The type of the media.</value> [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Audio; + public override MediaType MediaType => MediaType.Audio; public override double GetDefaultPrimaryImageAspectRatio() { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 87a021d41..98485f9a8 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -422,7 +422,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The type of the media.</value> [JsonIgnore] - public virtual string MediaType => null; + public virtual MediaType MediaType => MediaType.Unknown; [JsonIgnore] public virtual string[] PhysicalLocations diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index d75beb06d..63f9180fd 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Entities } [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Book; + public override MediaType MediaType => MediaType.Book; public override bool SupportsPlayedStatus => true; diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index c0bba60eb..555dd050c 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Controller.Entities ImageTypes = Array.Empty<ImageType>(); IncludeItemTypes = Array.Empty<BaseItemKind>(); ItemIds = Array.Empty<Guid>(); - MediaTypes = Array.Empty<string>(); + MediaTypes = Array.Empty<MediaType>(); MinSimilarityScore = 20; OfficialRatings = Array.Empty<string>(); OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); @@ -86,7 +86,7 @@ namespace MediaBrowser.Controller.Entities public bool? IncludeItemsByName { get; set; } - public string[] MediaTypes { get; set; } + public MediaType[] MediaTypes { get; set; } public BaseItemKind[] IncludeItemTypes { get; set; } diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs index ba6ce189a..cb9feacd3 100644 --- a/MediaBrowser.Controller/Entities/Photo.cs +++ b/MediaBrowser.Controller/Entities/Photo.cs @@ -3,6 +3,7 @@ #pragma warning disable CS1591 using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Drawing; namespace MediaBrowser.Controller.Entities @@ -13,7 +14,7 @@ namespace MediaBrowser.Controller.Entities public override bool SupportsLocalMetadata => false; [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Photo; + public override MediaType MediaType => MediaType.Photo; [JsonIgnore] public override Folder LatestItemsIndexContainer => AlbumEntity; diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index a134735b2..42431c832 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -476,7 +476,7 @@ namespace MediaBrowser.Controller.Entities public static bool Filter(BaseItem item, User user, InternalItemsQuery query, IUserDataManager userDataManager, ILibraryManager libraryManager) { - if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType)) { return false; } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 9f685b7e2..be2eb4d28 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -256,7 +257,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The type of the media.</value> [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Video; + public override MediaType MediaType => MediaType.Video; public override List<string> GetUserDataKeys() { diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index f11e3c8f6..3c2cf8e3d 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.LiveTv public override LocationType LocationType => LocationType.Remote; [JsonIgnore] - public override string MediaType => ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; + public override MediaType MediaType => ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video; [JsonIgnore] public bool IsMovie { get; set; } diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 498df5ab0..ca032e7f6 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.Controller.Playlists public override bool SupportsInheritedParentImages => false; [JsonIgnore] - public override bool SupportsPlayedStatus => string.Equals(MediaType, "Video", StringComparison.OrdinalIgnoreCase); + public override bool SupportsPlayedStatus => MediaType == Jellyfin.Data.Enums.MediaType.Video; [JsonIgnore] public override bool AlwaysScanInternalMetadataPath => true; @@ -80,10 +80,10 @@ namespace MediaBrowser.Controller.Playlists [JsonIgnore] public override bool IsPreSorted => true; - public string PlaylistMediaType { get; set; } + public MediaType PlaylistMediaType { get; set; } [JsonIgnore] - public override string MediaType => PlaylistMediaType; + public override MediaType MediaType => PlaylistMediaType; [JsonIgnore] private bool IsSharedItem @@ -107,9 +107,9 @@ namespace MediaBrowser.Controller.Playlists return System.IO.Path.HasExtension(path) && !Directory.Exists(path); } - public void SetMediaType(string value) + public void SetMediaType(MediaType? value) { - PlaylistMediaType = value; + PlaylistMediaType = value ?? MediaType.Unknown; } public override double GetDefaultPrimaryImageAspectRatio() @@ -167,7 +167,7 @@ namespace MediaBrowser.Controller.Playlists return base.GetChildren(user, true, query); } - public static List<BaseItem> GetPlaylistItems(string playlistMediaType, IEnumerable<BaseItem> inputItems, User user, DtoOptions options) + public static List<BaseItem> GetPlaylistItems(MediaType playlistMediaType, IEnumerable<BaseItem> inputItems, User user, DtoOptions options) { if (user is not null) { @@ -185,7 +185,7 @@ namespace MediaBrowser.Controller.Playlists return list; } - private static IEnumerable<BaseItem> GetPlaylistItems(BaseItem item, User user, string mediaType, DtoOptions options) + private static IEnumerable<BaseItem> GetPlaylistItems(BaseItem item, User user, MediaType mediaType, DtoOptions options) { if (item is MusicGenre musicGenre) { diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 172d79a59..3e30c8dc4 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; @@ -60,13 +61,13 @@ namespace MediaBrowser.Controller.Session /// Gets the playable media types. /// </summary> /// <value>The playable media types.</value> - public IReadOnlyList<string> PlayableMediaTypes + public IReadOnlyList<MediaType> PlayableMediaTypes { get { if (Capabilities is null) { - return Array.Empty<string>(); + return Array.Empty<MediaType>(); } return Capabilities.PlayableMediaTypes; diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs index 879a3616b..e0277870d 100644 --- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs @@ -1,5 +1,7 @@ +using System; using System.Collections.Generic; using System.Xml; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Playlists; @@ -31,7 +33,11 @@ namespace MediaBrowser.LocalMetadata.Parsers switch (reader.Name) { case "PlaylistMediaType": - item.PlaylistMediaType = reader.ReadNormalizedString(); + if (Enum.TryParse<MediaType>(reader.ReadNormalizedString(), out var mediaType)) + { + item.PlaylistMediaType = mediaType; + } + break; case "PlaylistItems": diff --git a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs index 847add07f..3f018cae9 100644 --- a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs @@ -1,6 +1,7 @@ using System.IO; using System.Threading.Tasks; using System.Xml; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -48,12 +49,12 @@ namespace MediaBrowser.LocalMetadata.Savers { var game = (Playlist)item; - if (string.IsNullOrEmpty(game.PlaylistMediaType)) + if (game.PlaylistMediaType == MediaType.Unknown) { return Task.CompletedTask; } - return writer.WriteElementStringAsync(null, "PlaylistMediaType", null, game.PlaylistMediaType); + return writer.WriteElementStringAsync(null, "PlaylistMediaType", null, game.PlaylistMediaType.ToString()); } /// <inheritdoc /> diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 07bb002ea..71d0896a7 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -1,6 +1,7 @@ #pragma warning disable CA1819 // Properties should not return arrays using System; using System.ComponentModel; +using System.Linq; using System.Xml.Serialization; using Jellyfin.Data.Enums; using Jellyfin.Extensions; @@ -227,9 +228,12 @@ namespace MediaBrowser.Model.Dlna /// The GetSupportedMediaTypes. /// </summary> /// <returns>The .</returns> - public string[] GetSupportedMediaTypes() + public MediaType[] GetSupportedMediaTypes() { - return ContainerProfile.SplitValue(SupportedMediaTypes); + return ContainerProfile.SplitValue(SupportedMediaTypes) + .Select(m => Enum.TryParse<MediaType>(m, out var parsed) ? parsed : MediaType.Unknown) + .Where(m => m != MediaType.Unknown) + .ToArray(); } /// <summary> diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 709039fc5..d257eab92 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -591,7 +591,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the type of the media. /// </summary> /// <value>The type of the media.</value> - public string MediaType { get; set; } + public MediaType MediaType { get; set; } /// <summary> /// Gets or sets the end date. diff --git a/MediaBrowser.Model/Entities/MediaType.cs b/MediaBrowser.Model/Entities/MediaType.cs deleted file mode 100644 index dd2ae810b..000000000 --- a/MediaBrowser.Model/Entities/MediaType.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace MediaBrowser.Model.Entities -{ - /// <summary> - /// Class MediaType. - /// </summary> - public static class MediaType - { - /// <summary> - /// The video. - /// </summary> - public const string Video = "Video"; - - /// <summary> - /// The audio. - /// </summary> - public const string Audio = "Audio"; - - /// <summary> - /// The photo. - /// </summary> - public const string Photo = "Photo"; - - /// <summary> - /// The book. - /// </summary> - public const string Book = "Book"; - } -} diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs index 847269716..62d496d04 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Playlists; @@ -22,7 +23,7 @@ public class PlaylistCreationRequest /// <summary> /// Gets or sets the media type. /// </summary> - public string? MediaType { get; set; } + public MediaType? MediaType { get; set; } /// <summary> /// Gets or sets the user id. diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs index 3fa7f3d56..fd911dbed 100644 --- a/MediaBrowser.Model/Search/SearchHint.cs +++ b/MediaBrowser.Model/Search/SearchHint.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Model.Search { Name = string.Empty; MatchedTerm = string.Empty; - MediaType = string.Empty; + MediaType = Jellyfin.Data.Enums.MediaType.Unknown; Artists = Array.Empty<string>(); } @@ -115,7 +115,7 @@ namespace MediaBrowser.Model.Search /// Gets or sets the type of the media. /// </summary> /// <value>The type of the media.</value> - public string MediaType { get; set; } + public MediaType MediaType { get; set; } /// <summary> /// Gets or sets the start date. diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs index 1caed827f..b91fd8657 100644 --- a/MediaBrowser.Model/Search/SearchQuery.cs +++ b/MediaBrowser.Model/Search/SearchQuery.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Model.Search IncludePeople = true; IncludeStudios = true; - MediaTypes = Array.Empty<string>(); + MediaTypes = Array.Empty<MediaType>(); IncludeItemTypes = Array.Empty<BaseItemKind>(); ExcludeItemTypes = Array.Empty<BaseItemKind>(); } @@ -55,7 +55,7 @@ namespace MediaBrowser.Model.Search public bool IncludeArtists { get; set; } - public string[] MediaTypes { get; set; } + public MediaType[] MediaTypes { get; set; } public BaseItemKind[] IncludeItemTypes { get; set; } diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs index d692906c6..7fefce9cd 100644 --- a/MediaBrowser.Model/Session/ClientCapabilities.cs +++ b/MediaBrowser.Model/Session/ClientCapabilities.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Dlna; namespace MediaBrowser.Model.Session @@ -11,12 +12,12 @@ namespace MediaBrowser.Model.Session { public ClientCapabilities() { - PlayableMediaTypes = Array.Empty<string>(); + PlayableMediaTypes = Array.Empty<MediaType>(); SupportedCommands = Array.Empty<GeneralCommandType>(); SupportsPersistentIdentifier = true; } - public IReadOnlyList<string> PlayableMediaTypes { get; set; } + public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index f5aff07db..219ed5d5f 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; using Jellyfin.Data.Enums; using AccessSchedule = Jellyfin.Data.Entities.AccessSchedule; @@ -174,8 +175,10 @@ namespace MediaBrowser.Model.Users public int RemoteClientBitrateLimit { get; set; } [XmlElement(ElementName = "AuthenticationProviderId")] + [Required(AllowEmptyStrings = false)] public string AuthenticationProviderId { get; set; } + [Required(AllowEmptyStrings= false)] public string PasswordResetProviderId { get; set; } /// <summary> diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index f21939d2a..6eb75891a 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -97,7 +97,7 @@ namespace MediaBrowser.Providers.MediaInfo { var query = new InternalItemsQuery { - MediaTypes = new string[] { MediaType.Video }, + MediaTypes = new[] { MediaType.Video }, IsVirtualItem = false, IncludeItemTypes = types, DtoOptions = new DtoOptions(true), diff --git a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs index 69f10b43b..90c2ff8dd 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs @@ -3,14 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Trickplay; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; -using TagLib.Ape; namespace MediaBrowser.Providers.Trickplay; diff --git a/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs new file mode 100644 index 000000000..3f965d0ff --- /dev/null +++ b/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using AutoFixture.Xunit2; +using Jellyfin.Api.Controllers; +using Jellyfin.Data.Entities; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.QuickConnect; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Users; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Moq; +using Nikse.SubtitleEdit.Core.Common; +using Xunit; + +namespace Jellyfin.Api.Tests.Controllers; + +public class UserControllerTests +{ + private readonly UserController _subject; + private readonly Mock<IUserManager> _mockUserManager; + private readonly Mock<ISessionManager> _mockSessionManager; + private readonly Mock<INetworkManager> _mockNetworkManager; + private readonly Mock<IDeviceManager> _mockDeviceManager; + private readonly Mock<IAuthorizationContext> _mockAuthorizationContext; + private readonly Mock<IServerConfigurationManager> _mockServerConfigurationManager; + private readonly Mock<ILogger<UserController>> _mockLogger; + private readonly Mock<IQuickConnect> _mockQuickConnect; + private readonly Mock<IPlaylistManager> _mockPlaylistManager; + + public UserControllerTests() + { + _mockUserManager = new Mock<IUserManager>(); + _mockSessionManager = new Mock<ISessionManager>(); + _mockNetworkManager = new Mock<INetworkManager>(); + _mockDeviceManager = new Mock<IDeviceManager>(); + _mockAuthorizationContext = new Mock<IAuthorizationContext>(); + _mockServerConfigurationManager = new Mock<IServerConfigurationManager>(); + _mockLogger = new Mock<ILogger<UserController>>(); + _mockQuickConnect = new Mock<IQuickConnect>(); + _mockPlaylistManager = new Mock<IPlaylistManager>(); + + _subject = new UserController( + _mockUserManager.Object, + _mockSessionManager.Object, + _mockNetworkManager.Object, + _mockDeviceManager.Object, + _mockAuthorizationContext.Object, + _mockServerConfigurationManager.Object, + _mockLogger.Object, + _mockQuickConnect.Object, + _mockPlaylistManager.Object); + } + + [Theory] + [AutoData] + public async Task UpdateUserPolicy_WhenUserNotFound_ReturnsNotFound(Guid userId, UserPolicy userPolicy) + { + User? nullUser = null; + _mockUserManager. + Setup(m => m.GetUserById(userId)) + .Returns(nullUser); + + Assert.IsType<NotFoundResult>(await _subject.UpdateUserPolicy(userId, userPolicy)); + } + + [Theory] + [InlineAutoData(null)] + [InlineAutoData("")] + [InlineAutoData(" ")] + public void UpdateUserPolicy_WhenPasswordResetProviderIdNotSupplied_ReturnsBadRequest(string? passwordResetProviderId) + { + var userPolicy = new UserPolicy + { + PasswordResetProviderId = passwordResetProviderId, + AuthenticationProviderId = "AuthenticationProviderId" + }; + + Assert.Contains( + Validate(userPolicy), v => + v.MemberNames.Contains("PasswordResetProviderId") && + v.ErrorMessage != null && + v.ErrorMessage.Contains("required", StringComparison.CurrentCultureIgnoreCase)); + } + + [Theory] + [InlineAutoData(null)] + [InlineAutoData("")] + [InlineAutoData(" ")] + public void UpdateUserPolicy_WhenAuthenticationProviderIdNotSupplied_ReturnsBadRequest(string? authenticationProviderId) + { + var userPolicy = new UserPolicy + { + AuthenticationProviderId = authenticationProviderId, + PasswordResetProviderId = "PasswordResetProviderId" + }; + + Assert.Contains(Validate(userPolicy), v => + v.MemberNames.Contains("AuthenticationProviderId") && + v.ErrorMessage != null && + v.ErrorMessage.Contains("required", StringComparison.CurrentCultureIgnoreCase)); + } + + private IList<ValidationResult> Validate(object model) + { + var result = new List<ValidationResult>(); + var context = new ValidationContext(model, null, null); + Validator.TryValidateObject(model, context, result, true); + + return result; + } +} |
