From 88b3490d1756236d0c2fc00243420d45d149a5d1 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 26 Mar 2024 15:29:48 +0100 Subject: Add playlist ACL endpoints --- .../Playlists/PlaylistManager.cs | 88 +++++++++++++++------- 1 file changed, 59 insertions(+), 29 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index aea8d65322..6724d54d1a 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -59,6 +59,11 @@ namespace Emby.Server.Implementations.Playlists _appConfig = appConfig; } + public Playlist GetPlaylist(Guid userId, Guid playlistId) + { + return GetPlaylists(userId).Where(p => p.Id.Equals(playlistId)).FirstOrDefault(); + } + public IEnumerable GetPlaylists(Guid userId) { var user = _userManager.GetUserById(userId); @@ -160,7 +165,7 @@ namespace Emby.Server.Implementations.Playlists } } - private string GetTargetPath(string path) + private static string GetTargetPath(string path) { while (Directory.Exists(path)) { @@ -231,13 +236,8 @@ namespace Emby.Server.Implementations.Playlists // Update the playlist in the repository playlist.LinkedChildren = newLinkedChildren; - await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - // Update the playlist on disk - if (playlist.IsFile) - { - SavePlaylistFile(playlist); - } + await UpdatePlaylist(playlist).ConfigureAwait(false); // Refresh playlist metadata _providerManager.QueueRefresh( @@ -266,12 +266,7 @@ namespace Emby.Server.Implementations.Playlists .Select(i => i.Item1) .ToArray(); - await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - - if (playlist.IsFile) - { - SavePlaylistFile(playlist); - } + await UpdatePlaylist(playlist).ConfigureAwait(false); _providerManager.QueueRefresh( playlist.Id, @@ -313,14 +308,9 @@ namespace Emby.Server.Implementations.Playlists newList.Insert(newIndex, item); } - playlist.LinkedChildren = newList.ToArray(); - - await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + playlist.LinkedChildren = [.. newList]; - if (playlist.IsFile) - { - SavePlaylistFile(playlist); - } + await UpdatePlaylist(playlist).ConfigureAwait(false); } /// @@ -430,8 +420,11 @@ namespace Emby.Server.Implementations.Playlists } else if (extension.Equals(".m3u8", StringComparison.OrdinalIgnoreCase)) { - var playlist = new M3uPlaylist(); - playlist.IsExtended = true; + var playlist = new M3uPlaylist + { + IsExtended = true + }; + foreach (var child in item.GetLinkedChildren()) { var entry = new M3uPlaylistEntry() @@ -481,7 +474,7 @@ namespace Emby.Server.Implementations.Playlists } } - private string NormalizeItemPath(string playlistPath, string itemPath) + private static string NormalizeItemPath(string playlistPath, string itemPath) { return MakeRelativePath(Path.GetDirectoryName(playlistPath), itemPath); } @@ -541,12 +534,7 @@ namespace Emby.Server.Implementations.Playlists { playlist.OwnerUserId = guid; playlist.Shares = rankedShares.Skip(1).ToArray(); - await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - - if (playlist.IsFile) - { - SavePlaylistFile(playlist); - } + await UpdatePlaylist(playlist).ConfigureAwait(false); } else if (!playlist.OpenAccess) { @@ -563,5 +551,47 @@ namespace Emby.Server.Implementations.Playlists } } } + + public async Task ToggleOpenAccess(Guid playlistId, Guid userId) + { + var playlist = GetPlaylist(userId, playlistId); + playlist.OpenAccess = !playlist.OpenAccess; + + await UpdatePlaylist(playlist).ConfigureAwait(false); + } + + public async Task AddToShares(Guid playlistId, Guid userId, Share share) + { + var playlist = GetPlaylist(userId, playlistId); + var shares = playlist.Shares.ToList(); + var existingUserShare = shares.FirstOrDefault(s => s.UserId?.Equals(share.UserId, StringComparison.OrdinalIgnoreCase) ?? false); + if (existingUserShare is not null) + { + shares.Remove(existingUserShare); + } + + shares.Add(share); + playlist.Shares = shares; + await UpdatePlaylist(playlist).ConfigureAwait(false); + } + + public async Task RemoveFromShares(Guid playlistId, Guid userId, Share share) + { + var playlist = GetPlaylist(userId, playlistId); + var shares = playlist.Shares.ToList(); + shares.Remove(share); + playlist.Shares = shares; + await UpdatePlaylist(playlist).ConfigureAwait(false); + } + + private async Task UpdatePlaylist(Playlist playlist) + { + await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + if (playlist.IsFile) + { + SavePlaylistFile(playlist); + } + } } } -- cgit v1.2.3 From f1dc1610a28fdb2dec4241d233503b6e11020546 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 26 Mar 2024 16:13:07 +0100 Subject: Extend playlist creation capabilities --- Emby.Server.Implementations/Playlists/PlaylistManager.cs | 10 +++------- Jellyfin.Api/Controllers/PlaylistsController.cs | 4 +++- Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs | 13 ++++++++++++- MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs | 7 ++++++- 4 files changed, 24 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 6724d54d1a..6b169db794 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -85,12 +85,7 @@ namespace Emby.Server.Implementations.Playlists { foreach (var itemId in options.ItemIdList) { - var item = _libraryManager.GetItemById(itemId); - if (item is null) - { - throw new ArgumentException("No item exists with the supplied Id"); - } - + var item = _libraryManager.GetItemById(itemId) ?? throw new ArgumentException("No item exists with the supplied Id"); if (item.MediaType != MediaType.Unknown) { options.MediaType = item.MediaType; @@ -139,7 +134,8 @@ namespace Emby.Server.Implementations.Playlists Name = name, Path = path, OwnerUserId = options.UserId, - Shares = options.Shares ?? Array.Empty() + Shares = options.Shares ?? [], + OpenAccess = options.OpenAccess ?? false }; playlist.SetMediaType(options.MediaType); diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index f0e8227fda..bf618e8fd7 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -92,7 +92,9 @@ public class PlaylistsController : BaseJellyfinApiController Name = name ?? createPlaylistRequest?.Name, ItemIdList = ids, UserId = userId.Value, - MediaType = mediaType ?? createPlaylistRequest?.MediaType + MediaType = mediaType ?? createPlaylistRequest?.MediaType, + Shares = createPlaylistRequest?.Shares.ToArray(), + OpenAccess = createPlaylistRequest?.OpenAccess }).ConfigureAwait(false); return result; diff --git a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs index bdc4888719..a82bff65ec 100644 --- a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs +++ b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Model.Entities; namespace Jellyfin.Api.Models.PlaylistDtos; @@ -20,7 +21,7 @@ public class CreatePlaylistDto /// Gets or sets item ids to add to the playlist. /// [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] - public IReadOnlyList Ids { get; set; } = Array.Empty(); + public IReadOnlyList Ids { get; set; } = []; /// /// Gets or sets the user id. @@ -31,4 +32,14 @@ public class CreatePlaylistDto /// Gets or sets the media type. /// public MediaType? MediaType { get; set; } + + /// + /// Gets or sets the shares. + /// + public IReadOnlyList Shares { get; set; } = []; + + /// + /// Gets or sets a value indicating whether open access is enabled. + /// + public bool OpenAccess { get; set; } } diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs index 62d496d047..93eccd5c77 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs @@ -33,5 +33,10 @@ public class PlaylistCreationRequest /// /// Gets or sets the shares. /// - public Share[]? Shares { get; set; } + public IReadOnlyList? Shares { get; set; } + + /// + /// Gets or sets a value indicating whether open access is enabled. + /// + public bool? OpenAccess { get; set; } } -- cgit v1.2.3 From 56c432a8439e1b75c15729bfdb19d41d34e3124d Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 26 Mar 2024 23:45:14 +0100 Subject: Apply review suggestions --- .../Library/Resolvers/PlaylistResolver.cs | 9 ++-- .../Playlists/PlaylistManager.cs | 14 +++--- Jellyfin.Api/Controllers/PlaylistsController.cs | 51 +++++++++++----------- .../Models/PlaylistDtos/CreatePlaylistDto.cs | 10 ++--- .../Playlists/IPlaylistManager.cs | 4 +- MediaBrowser.Controller/Playlists/Playlist.cs | 10 ++--- .../Parsers/BaseItemXmlParser.cs | 18 ++++---- MediaBrowser.Model/Entities/IHasShares.cs | 4 +- MediaBrowser.Model/Entities/Share.cs | 17 -------- MediaBrowser.Model/Entities/UserPermissions.cs | 19 ++++++++ .../Playlists/PlaylistCreationRequest.cs | 10 ++--- 11 files changed, 82 insertions(+), 84 deletions(-) delete mode 100644 MediaBrowser.Model/Entities/Share.cs create mode 100644 MediaBrowser.Model/Entities/UserPermissions.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index a50435ae69..a03c1214d6 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.IO; using System.Linq; @@ -11,7 +9,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Resolvers; using MediaBrowser.LocalMetadata.Savers; -using MediaBrowser.Model.Entities; namespace Emby.Server.Implementations.Library.Resolvers { @@ -20,11 +17,11 @@ namespace Emby.Server.Implementations.Library.Resolvers /// public class PlaylistResolver : GenericFolderResolver { - private CollectionType?[] _musicPlaylistCollectionTypes = - { + private readonly CollectionType?[] _musicPlaylistCollectionTypes = + [ null, CollectionType.music - }; + ]; /// protected override Playlist Resolve(ItemResolveArgs args) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 6b169db794..59c96852af 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -134,8 +134,8 @@ namespace Emby.Server.Implementations.Playlists Name = name, Path = path, OwnerUserId = options.UserId, - Shares = options.Shares ?? [], - OpenAccess = options.OpenAccess ?? false + Shares = options.Users ?? [], + OpenAccess = options.Public ?? false }; playlist.SetMediaType(options.MediaType); @@ -171,9 +171,9 @@ namespace Emby.Server.Implementations.Playlists return path; } - private List GetPlaylistItems(IEnumerable itemIds, MediaType playlistMediaType, User user, DtoOptions options) + private IReadOnlyList GetPlaylistItems(IEnumerable itemIds, MediaType playlistMediaType, User user, DtoOptions options) { - var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i is not null); + var items = itemIds.Select(_libraryManager.GetItemById).Where(i => i is not null); return Playlist.GetPlaylistItems(playlistMediaType, items, user, options); } @@ -556,11 +556,11 @@ namespace Emby.Server.Implementations.Playlists await UpdatePlaylist(playlist).ConfigureAwait(false); } - public async Task AddToShares(Guid playlistId, Guid userId, Share share) + public async Task AddToShares(Guid playlistId, Guid userId, UserPermissions share) { var playlist = GetPlaylist(userId, playlistId); var shares = playlist.Shares.ToList(); - var existingUserShare = shares.FirstOrDefault(s => s.UserId?.Equals(share.UserId, StringComparison.OrdinalIgnoreCase) ?? false); + var existingUserShare = shares.FirstOrDefault(s => s.UserId.Equals(share.UserId, StringComparison.OrdinalIgnoreCase)); if (existingUserShare is not null) { shares.Remove(existingUserShare); @@ -571,7 +571,7 @@ namespace Emby.Server.Implementations.Playlists await UpdatePlaylist(playlist).ConfigureAwait(false); } - public async Task RemoveFromShares(Guid playlistId, Guid userId, Share share) + public async Task RemoveFromShares(Guid playlistId, Guid userId, UserPermissions share) { var playlist = GetPlaylist(userId, playlistId); var shares = playlist.Shares.ToList(); diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index bf618e8fd7..c38061c7d6 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -93,32 +93,32 @@ public class PlaylistsController : BaseJellyfinApiController ItemIdList = ids, UserId = userId.Value, MediaType = mediaType ?? createPlaylistRequest?.MediaType, - Shares = createPlaylistRequest?.Shares.ToArray(), - OpenAccess = createPlaylistRequest?.OpenAccess + Users = createPlaylistRequest?.Users.ToArray() ?? [], + Public = createPlaylistRequest?.Public }).ConfigureAwait(false); return result; } /// - /// Get a playlist's shares. + /// Get a playlist's users. /// /// The playlist id. /// - /// A list of objects. + /// A list of objects. /// - [HttpGet("{playlistId}/Shares")] + [HttpGet("{playlistId}/User")] [ProducesResponseType(StatusCodes.Status200OK)] - public IReadOnlyList GetPlaylistShares( + public IReadOnlyList GetPlaylistUsers( [FromRoute, Required] Guid playlistId) { var userId = RequestHelpers.GetUserId(User, default); var playlist = _playlistManager.GetPlaylist(userId, playlistId); var isPermitted = playlist.OwnerUserId.Equals(userId) - || playlist.Shares.Any(s => s.CanEdit && (s.UserId?.Equals(userId) ?? false)); + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(userId)); - return isPermitted ? playlist.Shares : new List(); + return isPermitted ? playlist.Shares : []; } /// @@ -131,14 +131,14 @@ public class PlaylistsController : BaseJellyfinApiController /// [HttpPost("{playlistId}/ToggleOpenAccess")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task ToggleopenAccess( + public async Task ToggleOpenAccess( [FromRoute, Required] Guid playlistId) { var callingUserId = RequestHelpers.GetUserId(User, default); var playlist = _playlistManager.GetPlaylist(callingUserId, playlistId); var isPermitted = playlist.OwnerUserId.Equals(callingUserId) - || playlist.Shares.Any(s => s.CanEdit && (s.UserId?.Equals(callingUserId) ?? false)); + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); if (!isPermitted) { @@ -151,35 +151,34 @@ public class PlaylistsController : BaseJellyfinApiController } /// - /// Adds shares to a playlist's shares. + /// Upsert a user to a playlist's users. /// /// The playlist id. - /// The shares. + /// The user id. + /// Edit permission. /// - /// A that represents the asynchronous operation to add shares to a playlist. + /// A that represents the asynchronous operation to upsert an user to a playlist. /// The task result contains an indicating success. /// - [HttpPost("{playlistId}/Shares")] + [HttpPost("{playlistId}/User/{userId}")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task AddUserToPlaylistShares( + public async Task AddUserToPlaylist( [FromRoute, Required] Guid playlistId, - [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Share[] shares) + [FromRoute, Required] Guid userId, + [FromBody] bool canEdit) { var callingUserId = RequestHelpers.GetUserId(User, default); var playlist = _playlistManager.GetPlaylist(callingUserId, playlistId); var isPermitted = playlist.OwnerUserId.Equals(callingUserId) - || playlist.Shares.Any(s => s.CanEdit && (s.UserId?.Equals(callingUserId) ?? false)); + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); if (!isPermitted) { return Unauthorized("Unauthorized access"); } - foreach (var share in shares) - { - await _playlistManager.AddToShares(playlistId, callingUserId, share).ConfigureAwait(false); - } + await _playlistManager.AddToShares(playlistId, callingUserId, new UserPermissions(userId.ToString(), canEdit)).ConfigureAwait(false); return NoContent(); } @@ -193,24 +192,24 @@ public class PlaylistsController : BaseJellyfinApiController /// A that represents the asynchronous operation to delete a user from a playlist's shares. /// The task result contains an indicating success. /// - [HttpDelete("{playlistId}/Shares")] + [HttpDelete("{playlistId}/User/{userId}")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task RemoveUserFromPlaylistShares( + public async Task RemoveUserFromPlaylist( [FromRoute, Required] Guid playlistId, - [FromBody] Guid userId) + [FromRoute, Required] Guid userId) { var callingUserId = RequestHelpers.GetUserId(User, default); var playlist = _playlistManager.GetPlaylist(callingUserId, playlistId); var isPermitted = playlist.OwnerUserId.Equals(callingUserId) - || playlist.Shares.Any(s => s.CanEdit && (s.UserId?.Equals(callingUserId) ?? false)); + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); if (!isPermitted) { return Unauthorized("Unauthorized access"); } - var share = playlist.Shares.FirstOrDefault(s => s.UserId?.Equals(userId) ?? false); + var share = playlist.Shares.FirstOrDefault(s => s.UserId.Equals(userId)); if (share is null) { diff --git a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs index a82bff65ec..6eedd21316 100644 --- a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs +++ b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs @@ -15,7 +15,7 @@ public class CreatePlaylistDto /// /// Gets or sets the name of the new playlist. /// - public string? Name { get; set; } + public required string Name { get; set; } /// /// Gets or sets item ids to add to the playlist. @@ -34,12 +34,12 @@ public class CreatePlaylistDto public MediaType? MediaType { get; set; } /// - /// Gets or sets the shares. + /// Gets or sets the playlist users. /// - public IReadOnlyList Shares { get; set; } = []; + public IReadOnlyList Users { get; set; } = []; /// - /// Gets or sets a value indicating whether open access is enabled. + /// Gets or sets a value indicating whether the playlist is public. /// - public bool OpenAccess { get; set; } + public bool Public { get; set; } = true; } diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs index aaca1cc492..238923d296 100644 --- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs +++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs @@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.Playlists /// The user identifier. /// The share. /// Task. - Task AddToShares(Guid playlistId, Guid userId, Share share); + Task AddToShares(Guid playlistId, Guid userId, UserPermissions share); /// /// Rremoves a share from the playlist. @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.Playlists /// The user identifier. /// The share. /// Task. - Task RemoveFromShares(Guid playlistId, Guid userId, Share share); + Task RemoveFromShares(Guid playlistId, Guid userId, UserPermissions share); /// /// Creates the playlist. diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 9a08a4ce3e..dfd9b83302 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Playlists public Playlist() { - Shares = Array.Empty(); + Shares = []; OpenAccess = false; } @@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Playlists public bool OpenAccess { get; set; } - public IReadOnlyList Shares { get; set; } + public IReadOnlyList Shares { get; set; } [JsonIgnore] public bool IsFile => IsPlaylistFile(Path); @@ -129,7 +129,7 @@ namespace MediaBrowser.Controller.Playlists protected override List LoadChildren() { // Save a trip to the database - return new List(); + return []; } protected override Task ValidateChildrenInternal(IProgress progress, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService, CancellationToken cancellationToken) @@ -144,7 +144,7 @@ namespace MediaBrowser.Controller.Playlists protected override IEnumerable GetNonCachedChildren(IDirectoryService directoryService) { - return new List(); + return []; } public override IEnumerable GetRecursiveChildren(User user, InternalItemsQuery query) @@ -166,7 +166,7 @@ namespace MediaBrowser.Controller.Playlists return base.GetChildren(user, true, query); } - public static List GetPlaylistItems(MediaType playlistMediaType, IEnumerable inputItems, User user, DtoOptions options) + public static IReadOnlyList GetPlaylistItems(MediaType playlistMediaType, IEnumerable inputItems, User user, DtoOptions options) { if (user is not null) { diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 8a870e0d9b..4ee1b2ef69 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -519,7 +519,7 @@ namespace MediaBrowser.LocalMetadata.Parsers private void FetchFromSharesNode(XmlReader reader, IHasShares item) { - var list = new List(); + var list = new List(); reader.MoveToContent(); reader.Read(); @@ -565,7 +565,7 @@ namespace MediaBrowser.LocalMetadata.Parsers } } - item.Shares = list.ToArray(); + item.Shares = [.. list]; } /// @@ -830,12 +830,12 @@ namespace MediaBrowser.LocalMetadata.Parsers /// /// The xml reader. /// The share. - protected Share? GetShare(XmlReader reader) + protected UserPermissions? GetShare(XmlReader reader) { - var item = new Share(); - reader.MoveToContent(); reader.Read(); + string? userId = null; + var canEdit = false; // Loop through each element while (!reader.EOF && reader.ReadState == ReadState.Interactive) @@ -845,10 +845,10 @@ namespace MediaBrowser.LocalMetadata.Parsers switch (reader.Name) { case "UserId": - item.UserId = reader.ReadNormalizedString(); + userId = reader.ReadNormalizedString(); break; case "CanEdit": - item.CanEdit = string.Equals(reader.ReadElementContentAsString(), "true", StringComparison.OrdinalIgnoreCase); + canEdit = string.Equals(reader.ReadElementContentAsString(), "true", StringComparison.OrdinalIgnoreCase); break; default: reader.Skip(); @@ -862,9 +862,9 @@ namespace MediaBrowser.LocalMetadata.Parsers } // This is valid - if (!string.IsNullOrWhiteSpace(item.UserId)) + if (!string.IsNullOrWhiteSpace(userId)) { - return item; + return new UserPermissions(userId, canEdit); } return null; diff --git a/MediaBrowser.Model/Entities/IHasShares.cs b/MediaBrowser.Model/Entities/IHasShares.cs index 31574a3ffa..fb6b6e424b 100644 --- a/MediaBrowser.Model/Entities/IHasShares.cs +++ b/MediaBrowser.Model/Entities/IHasShares.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace MediaBrowser.Model.Entities; @@ -10,5 +10,5 @@ public interface IHasShares /// /// Gets or sets the shares. /// - IReadOnlyList Shares { get; set; } + IReadOnlyList Shares { get; set; } } diff --git a/MediaBrowser.Model/Entities/Share.cs b/MediaBrowser.Model/Entities/Share.cs deleted file mode 100644 index 186aad1892..0000000000 --- a/MediaBrowser.Model/Entities/Share.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace MediaBrowser.Model.Entities; - -/// -/// Class to hold data on sharing permissions. -/// -public class Share -{ - /// - /// Gets or sets the user id. - /// - public string? UserId { get; set; } - - /// - /// Gets or sets a value indicating whether the user has edit permissions. - /// - public bool CanEdit { get; set; } -} diff --git a/MediaBrowser.Model/Entities/UserPermissions.cs b/MediaBrowser.Model/Entities/UserPermissions.cs new file mode 100644 index 0000000000..271feed114 --- /dev/null +++ b/MediaBrowser.Model/Entities/UserPermissions.cs @@ -0,0 +1,19 @@ +namespace MediaBrowser.Model.Entities; + +/// +/// Class to hold data on user permissions for lists. +/// +/// The user id. +/// Edit permission. +public class UserPermissions(string userId, bool canEdit = false) +{ + /// + /// Gets or sets the user id. + /// + public string UserId { get; set; } = userId; + + /// + /// Gets or sets a value indicating whether the user has edit permissions. + /// + public bool CanEdit { get; set; } = canEdit; +} diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs index 93eccd5c77..f1351588fb 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs @@ -18,7 +18,7 @@ public class PlaylistCreationRequest /// /// Gets or sets the list of items. /// - public IReadOnlyList ItemIdList { get; set; } = Array.Empty(); + public IReadOnlyList ItemIdList { get; set; } = []; /// /// Gets or sets the media type. @@ -31,12 +31,12 @@ public class PlaylistCreationRequest public Guid UserId { get; set; } /// - /// Gets or sets the shares. + /// Gets or sets the user permissions. /// - public IReadOnlyList? Shares { get; set; } + public IReadOnlyList Users { get; set; } = []; /// - /// Gets or sets a value indicating whether open access is enabled. + /// Gets or sets a value indicating whether the playlist is public. /// - public bool? OpenAccess { get; set; } + public bool? Public { get; set; } = true; } -- cgit v1.2.3 From 4201079b349c34372aa9375791aa86d7e90572f1 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 30 Mar 2024 17:30:00 +0100 Subject: fix: use a reentrant lock when accessing active connections (#11256) --- .../HttpServer/WebSocketManager.cs | 20 ++++++------ .../Net/BasePeriodicWebSocketListener.cs | 37 ++++------------------ 2 files changed, 17 insertions(+), 40 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 52f14b0b10..774d3563cb 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.HttpServer WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); - using var connection = new WebSocketConnection( + var connection = new WebSocketConnection( _loggerFactory.CreateLogger(), webSocket, authorizationInfo, @@ -56,17 +56,19 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived }; - - var tasks = new Task[_webSocketListeners.Length]; - for (var i = 0; i < _webSocketListeners.Length; ++i) + await using (connection.ConfigureAwait(false)) { - tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context); - } + var tasks = new Task[_webSocketListeners.Length]; + for (var i = 0; i < _webSocketListeners.Length; ++i) + { + tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context); + } - await Task.WhenAll(tasks).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); - await connection.ReceiveAsync().ConfigureAwait(false); - _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); + await connection.ReceiveAsync().ConfigureAwait(false); + _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); + } } catch (Exception ex) // Otherwise ASP.Net will ignore the exception { diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 219da309e4..06386f2b86 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.Net SingleWriter = false }); - private readonly SemaphoreSlim _lock = new(1, 1); + private readonly object _activeConnectionsLock = new(); /// /// The _active connections. @@ -126,15 +126,10 @@ namespace MediaBrowser.Controller.Net InitialDelayMs = dueTimeMs }; - _lock.Wait(); - try + lock (_activeConnectionsLock) { _activeConnections.Add((message.Connection, cancellationTokenSource, state)); } - finally - { - _lock.Release(); - } } protected void SendData(bool force) @@ -153,8 +148,7 @@ namespace MediaBrowser.Controller.Net (IWebSocketConnection Connection, CancellationTokenSource CancellationTokenSource, TStateType State)[] tuples; var now = DateTime.UtcNow; - await _lock.WaitAsync().ConfigureAwait(false); - try + lock (_activeConnectionsLock) { if (_activeConnections.Count == 0) { @@ -174,10 +168,6 @@ namespace MediaBrowser.Controller.Net }) .ToArray(); } - finally - { - _lock.Release(); - } if (tuples.Length == 0) { @@ -240,8 +230,7 @@ namespace MediaBrowser.Controller.Net /// The message. private void Stop(WebSocketMessageInfo message) { - _lock.Wait(); - try + lock (_activeConnectionsLock) { var connection = _activeConnections.FirstOrDefault(c => c.Connection == message.Connection); @@ -250,10 +239,6 @@ namespace MediaBrowser.Controller.Net DisposeConnection(connection); } } - finally - { - _lock.Release(); - } } /// @@ -283,15 +268,10 @@ namespace MediaBrowser.Controller.Net Logger.LogError(ex, "Error disposing websocket"); } - _lock.Wait(); - try + lock (_activeConnectionsLock) { _activeConnections.Remove(connection); } - finally - { - _lock.Release(); - } } protected virtual async ValueTask DisposeAsyncCore() @@ -306,18 +286,13 @@ namespace MediaBrowser.Controller.Net Logger.LogError(ex, "Disposing the message consumer failed"); } - await _lock.WaitAsync().ConfigureAwait(false); - try + lock (_activeConnectionsLock) { foreach (var connection in _activeConnections.ToArray()) { DisposeConnection(connection); } } - finally - { - _lock.Release(); - } } /// -- cgit v1.2.3 From 84b933d8355c718d3674f3b7371a190849071970 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sun, 31 Mar 2024 22:48:46 +0200 Subject: Use enum for BaseItemDto.ExtraType (#11261) --- Emby.Server.Implementations/Dto/DtoService.cs | 10 ++-------- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 7812687ea3..5da9bea262 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -903,10 +903,7 @@ namespace Emby.Server.Implementations.Dto if (item is Audio audio) { dto.Album = audio.Album; - if (audio.ExtraType.HasValue) - { - dto.ExtraType = audio.ExtraType.Value.ToString(); - } + dto.ExtraType = audio.ExtraType; var albumParent = audio.AlbumEntity; @@ -1058,10 +1055,7 @@ namespace Emby.Server.Implementations.Dto dto.Trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult(); } - if (video.ExtraType.HasValue) - { - dto.ExtraType = video.ExtraType.Value.ToString(); - } + dto.ExtraType = video.ExtraType; } if (options.ContainsField(ItemFields.MediaStreams)) diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index cfff717db2..6d5c84e1de 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -65,7 +65,7 @@ namespace MediaBrowser.Model.Dto public DateTime? DateLastMediaAdded { get; set; } - public string ExtraType { get; set; } + public ExtraType? ExtraType { get; set; } public int? AirsBeforeSeasonNumber { get; set; } -- cgit v1.2.3 From d9fe900952db446ded5ebdb937bd9e242b4a96de Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sun, 31 Mar 2024 22:48:56 +0200 Subject: Fix FindExtras overwriting current extra type (#11260) --- Emby.Server.Implementations/Library/LibraryManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index a2abafd2ae..0c854bdb74 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2677,7 +2677,12 @@ namespace Emby.Server.Implementations.Library extra = itemById; } - extra.ExtraType = extraType; + // Only update extra type if it is more specific then the currently known extra type + if (extra.ExtraType is null or ExtraType.Unknown || extraType != ExtraType.Unknown) + { + extra.ExtraType = extraType; + } + extra.ParentId = Guid.Empty; extra.OwnerId = owner.Id; return extra; -- cgit v1.2.3 From 3ade3a8e63204becb856bbc268ce877c82a44a2c Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 31 Mar 2024 21:58:06 -0600 Subject: Lowercase CollectionTypeOptions to match legacy experience (#11272) --- .../Collections/CollectionManager.cs | 2 +- .../Entities/CollectionTypeOptions.cs | 59 ++++++++++++++---- MediaBrowser.Model/Entities/VirtualFolderInfo.cs | 1 - .../Json/Converters/JsonLowerCaseConverter.cs | 25 -------- .../Recordings/RecordingsManager.cs | 4 +- .../Json/Converters/JsonLowerCaseConverterTests.cs | 71 ---------------------- 6 files changed, 49 insertions(+), 113 deletions(-) delete mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs delete mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index b34d0f21ef..e414792ba0 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Collections var name = _localizationManager.GetLocalizedString("Collections"); - await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.BoxSets, libraryOptions, true).ConfigureAwait(false); + await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.boxsets, libraryOptions, true).ConfigureAwait(false); return FindFolders(path).First(); } diff --git a/MediaBrowser.Model/Entities/CollectionTypeOptions.cs b/MediaBrowser.Model/Entities/CollectionTypeOptions.cs index e1894d84ad..fc4cfdd66c 100644 --- a/MediaBrowser.Model/Entities/CollectionTypeOptions.cs +++ b/MediaBrowser.Model/Entities/CollectionTypeOptions.cs @@ -1,16 +1,49 @@ -#pragma warning disable CS1591 +#pragma warning disable SA1300 // Lowercase required for backwards compat. -namespace MediaBrowser.Model.Entities +namespace MediaBrowser.Model.Entities; + +/// +/// The collection type options. +/// +public enum CollectionTypeOptions { - public enum CollectionTypeOptions - { - Movies = 0, - TvShows = 1, - Music = 2, - MusicVideos = 3, - HomeVideos = 4, - BoxSets = 5, - Books = 6, - Mixed = 7 - } + /// + /// Movies. + /// + movies = 0, + + /// + /// TV Shows. + /// + tvshows = 1, + + /// + /// Music. + /// + music = 2, + + /// + /// Music Videos. + /// + musicvideos = 3, + + /// + /// Home Videos (and Photos). + /// + homevideos = 4, + + /// + /// Box Sets. + /// + boxsets = 5, + + /// + /// Books. + /// + books = 6, + + /// + /// Mixed Movies and TV Shows. + /// + mixed = 7 } diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs index 2b2bda12c3..89bb72c3c8 100644 --- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs +++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs @@ -37,7 +37,6 @@ namespace MediaBrowser.Model.Entities /// Gets or sets the type of the collection. /// /// The type of the collection. - [JsonConverter(typeof(JsonLowerCaseConverter))] public CollectionTypeOptions? CollectionType { get; set; } public LibraryOptions LibraryOptions { get; set; } diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs deleted file mode 100644 index cd582ced64..0000000000 --- a/src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Jellyfin.Extensions.Json.Converters -{ - /// - /// Converts an object to a lowercase string. - /// - /// The object type. - public class JsonLowerCaseConverter : JsonConverter - { - /// - public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return JsonSerializer.Deserialize(ref reader, options); - } - - /// - public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) - { - writer.WriteStringValue(value?.ToString()?.ToLowerInvariant()); - } - } -} diff --git a/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs b/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs index 92605a1eb9..2f4caa3867 100644 --- a/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs +++ b/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs @@ -159,7 +159,7 @@ public sealed class RecordingsManager : IRecordingsManager, IDisposable { Locations = [customPath], Name = "Recorded Movies", - CollectionType = CollectionTypeOptions.Movies + CollectionType = CollectionTypeOptions.movies }; } @@ -172,7 +172,7 @@ public sealed class RecordingsManager : IRecordingsManager, IDisposable { Locations = [customPath], Name = "Recorded Shows", - CollectionType = CollectionTypeOptions.TvShows + CollectionType = CollectionTypeOptions.tvshows }; } } diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs deleted file mode 100644 index 16c69ca489..0000000000 --- a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using Jellyfin.Extensions.Json.Converters; -using MediaBrowser.Model.Entities; -using Xunit; - -namespace Jellyfin.Extensions.Tests.Json.Converters -{ - public class JsonLowerCaseConverterTests - { - private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions() - { - Converters = - { - new JsonStringEnumConverter() - } - }; - - [Theory] - [InlineData(null, "{\"CollectionType\":null}")] - [InlineData(CollectionTypeOptions.Movies, "{\"CollectionType\":\"movies\"}")] - [InlineData(CollectionTypeOptions.MusicVideos, "{\"CollectionType\":\"musicvideos\"}")] - public void Serialize_CollectionTypeOptions_Correct(CollectionTypeOptions? collectionType, string expected) - { - Assert.Equal(expected, JsonSerializer.Serialize(new TestContainer(collectionType), _jsonOptions)); - } - - [Theory] - [InlineData("{\"CollectionType\":null}", null)] - [InlineData("{\"CollectionType\":\"movies\"}", CollectionTypeOptions.Movies)] - [InlineData("{\"CollectionType\":\"musicvideos\"}", CollectionTypeOptions.MusicVideos)] - public void Deserialize_CollectionTypeOptions_Correct(string json, CollectionTypeOptions? result) - { - var res = JsonSerializer.Deserialize(json, _jsonOptions); - Assert.NotNull(res); - Assert.Equal(result, res!.CollectionType); - } - - [Theory] - [InlineData(null)] - [InlineData(CollectionTypeOptions.Movies)] - [InlineData(CollectionTypeOptions.MusicVideos)] - public void RoundTrip_CollectionTypeOptions_Correct(CollectionTypeOptions? value) - { - var res = JsonSerializer.Deserialize(JsonSerializer.Serialize(new TestContainer(value), _jsonOptions), _jsonOptions); - Assert.NotNull(res); - Assert.Equal(value, res!.CollectionType); - } - - [Theory] - [InlineData("{\"CollectionType\":null}")] - [InlineData("{\"CollectionType\":\"movies\"}")] - [InlineData("{\"CollectionType\":\"musicvideos\"}")] - public void RoundTrip_String_Correct(string json) - { - var res = JsonSerializer.Serialize(JsonSerializer.Deserialize(json, _jsonOptions), _jsonOptions); - Assert.Equal(json, res); - } - - private sealed class TestContainer - { - public TestContainer(CollectionTypeOptions? collectionType) - { - CollectionType = collectionType; - } - - [JsonConverter(typeof(JsonLowerCaseConverter))] - public CollectionTypeOptions? CollectionType { get; set; } - } - } -} -- cgit v1.2.3 From e12c666f709a25de0a8e3ab0487463158fdbb202 Mon Sep 17 00:00:00 2001 From: David Davó Date: Mon, 1 Apr 2024 11:43:48 +0000 Subject: Translated using Weblate (Spanish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/ --- Emby.Server.Implementations/Localization/Core/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index fe10be3085..91e29d9266 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -30,7 +30,7 @@ "ItemAddedWithName": "{0} se ha añadido a la biblioteca", "ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca", "LabelIpAddressValue": "Dirección IP: {0}", - "LabelRunningTimeValue": "Tiempo de funcionamiento: {0}", + "LabelRunningTimeValue": "Duración: {0}", "Latest": "Últimas", "MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin", "MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}", -- cgit v1.2.3 From bff37ed13aa9ee0267ee5e1248339c6044fa1b0c Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 1 Apr 2024 19:59:48 +0200 Subject: Apply review suggestions --- .../Playlists/PlaylistManager.cs | 10 ++++---- Jellyfin.Api/Controllers/PlaylistsController.cs | 6 ++--- .../Models/PlaylistDtos/CreatePlaylistDto.cs | 2 +- .../Playlists/IPlaylistManager.cs | 4 +-- MediaBrowser.Controller/Playlists/Playlist.cs | 2 +- .../Parsers/BaseItemXmlParser.cs | 6 ++--- MediaBrowser.Model/Entities/IHasShares.cs | 2 +- .../Entities/PlaylistUserPermissions.cs | 30 ++++++++++++++++++++++ MediaBrowser.Model/Entities/UserPermissions.cs | 28 -------------------- .../Playlists/PlaylistCreationRequest.cs | 2 +- 10 files changed, 47 insertions(+), 45 deletions(-) create mode 100644 MediaBrowser.Model/Entities/PlaylistUserPermissions.cs delete mode 100644 MediaBrowser.Model/Entities/UserPermissions.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 59c96852af..0e5add635d 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -526,9 +526,9 @@ namespace Emby.Server.Implementations.Playlists { // Update owner if shared var rankedShares = playlist.Shares.OrderByDescending(x => x.CanEdit).ToArray(); - if (rankedShares.Length > 0 && Guid.TryParse(rankedShares[0].UserId, out var guid)) + if (rankedShares.Length > 0) { - playlist.OwnerUserId = guid; + playlist.OwnerUserId = rankedShares[0].UserId; playlist.Shares = rankedShares.Skip(1).ToArray(); await UpdatePlaylist(playlist).ConfigureAwait(false); } @@ -556,11 +556,11 @@ namespace Emby.Server.Implementations.Playlists await UpdatePlaylist(playlist).ConfigureAwait(false); } - public async Task AddToShares(Guid playlistId, Guid userId, UserPermissions share) + public async Task AddToShares(Guid playlistId, Guid userId, PlaylistUserPermissions share) { var playlist = GetPlaylist(userId, playlistId); var shares = playlist.Shares.ToList(); - var existingUserShare = shares.FirstOrDefault(s => s.UserId.Equals(share.UserId, StringComparison.OrdinalIgnoreCase)); + var existingUserShare = shares.FirstOrDefault(s => s.UserId.Equals(share.UserId)); if (existingUserShare is not null) { shares.Remove(existingUserShare); @@ -571,7 +571,7 @@ namespace Emby.Server.Implementations.Playlists await UpdatePlaylist(playlist).ConfigureAwait(false); } - public async Task RemoveFromShares(Guid playlistId, Guid userId, UserPermissions share) + public async Task RemoveFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share) { var playlist = GetPlaylist(userId, playlistId); var shares = playlist.Shares.ToList(); diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index 7ca04d7ba5..862e5235ee 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -108,13 +108,13 @@ public class PlaylistsController : BaseJellyfinApiController /// Unauthorized access. /// Playlist not found. /// - /// A list of objects. + /// A list of objects. /// [HttpGet("{playlistId}/User")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult> GetPlaylistUsers( + public ActionResult> GetPlaylistUsers( [FromRoute, Required] Guid playlistId) { var userId = User.GetUserId(); @@ -206,7 +206,7 @@ public class PlaylistsController : BaseJellyfinApiController return Unauthorized("Unauthorized access"); } - await _playlistManager.AddToShares(playlistId, callingUserId, new UserPermissions(userId.ToString(), canEdit)).ConfigureAwait(false); + await _playlistManager.AddToShares(playlistId, callingUserId, new PlaylistUserPermissions(userId.ToString(), canEdit)).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs index 6eedd21316..69694a7699 100644 --- a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs +++ b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs @@ -36,7 +36,7 @@ public class CreatePlaylistDto /// /// Gets or sets the playlist users. /// - public IReadOnlyList Users { get; set; } = []; + public IReadOnlyList Users { get; set; } = []; /// /// Gets or sets a value indicating whether the playlist is public. diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs index 238923d296..1750be6199 100644 --- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs +++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs @@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.Playlists /// The user identifier. /// The share. /// Task. - Task AddToShares(Guid playlistId, Guid userId, UserPermissions share); + Task AddToShares(Guid playlistId, Guid userId, PlaylistUserPermissions share); /// /// Rremoves a share from the playlist. @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.Playlists /// The user identifier. /// The share. /// Task. - Task RemoveFromShares(Guid playlistId, Guid userId, UserPermissions share); + Task RemoveFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share); /// /// Creates the playlist. diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index dfd9b83302..b948d2e18b 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Playlists public bool OpenAccess { get; set; } - public IReadOnlyList Shares { get; set; } + public IReadOnlyList Shares { get; set; } [JsonIgnore] public bool IsFile => IsPlaylistFile(Path); diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 4ee1b2ef69..22ae3f12b2 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -519,7 +519,7 @@ namespace MediaBrowser.LocalMetadata.Parsers private void FetchFromSharesNode(XmlReader reader, IHasShares item) { - var list = new List(); + var list = new List(); reader.MoveToContent(); reader.Read(); @@ -830,7 +830,7 @@ namespace MediaBrowser.LocalMetadata.Parsers /// /// The xml reader. /// The share. - protected UserPermissions? GetShare(XmlReader reader) + protected PlaylistUserPermissions? GetShare(XmlReader reader) { reader.MoveToContent(); reader.Read(); @@ -864,7 +864,7 @@ namespace MediaBrowser.LocalMetadata.Parsers // This is valid if (!string.IsNullOrWhiteSpace(userId)) { - return new UserPermissions(userId, canEdit); + return new PlaylistUserPermissions(userId, canEdit); } return null; diff --git a/MediaBrowser.Model/Entities/IHasShares.cs b/MediaBrowser.Model/Entities/IHasShares.cs index fb6b6e424b..8c4ba6c425 100644 --- a/MediaBrowser.Model/Entities/IHasShares.cs +++ b/MediaBrowser.Model/Entities/IHasShares.cs @@ -10,5 +10,5 @@ public interface IHasShares /// /// Gets or sets the shares. /// - IReadOnlyList Shares { get; set; } + IReadOnlyList Shares { get; set; } } diff --git a/MediaBrowser.Model/Entities/PlaylistUserPermissions.cs b/MediaBrowser.Model/Entities/PlaylistUserPermissions.cs new file mode 100644 index 0000000000..b5f017d2bf --- /dev/null +++ b/MediaBrowser.Model/Entities/PlaylistUserPermissions.cs @@ -0,0 +1,30 @@ +using System; + +namespace MediaBrowser.Model.Entities; + +/// +/// Class to hold data on user permissions for playlists. +/// +public class PlaylistUserPermissions +{ + /// + /// Initializes a new instance of the class. + /// + /// The user id. + /// Edit permission. + public PlaylistUserPermissions(Guid userId, bool canEdit = false) + { + UserId = userId; + CanEdit = canEdit; + } + + /// + /// Gets or sets the user id. + /// + public Guid UserId { get; set; } + + /// + /// Gets or sets a value indicating whether the user has edit permissions. + /// + public bool CanEdit { get; set; } +} diff --git a/MediaBrowser.Model/Entities/UserPermissions.cs b/MediaBrowser.Model/Entities/UserPermissions.cs deleted file mode 100644 index 80e2cd32c2..0000000000 --- a/MediaBrowser.Model/Entities/UserPermissions.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace MediaBrowser.Model.Entities; - -/// -/// Class to hold data on user permissions for lists. -/// -public class UserPermissions -{ - /// - /// Initializes a new instance of the class. - /// - /// The user id. - /// Edit permission. - public UserPermissions(string userId, bool canEdit = false) - { - UserId = userId; - CanEdit = canEdit; - } - - /// - /// Gets or sets the user id. - /// - public string UserId { get; set; } - - /// - /// Gets or sets a value indicating whether the user has edit permissions. - /// - public bool CanEdit { get; set; } -} diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs index f1351588fb..ec54b1afd3 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs @@ -33,7 +33,7 @@ public class PlaylistCreationRequest /// /// Gets or sets the user permissions. /// - public IReadOnlyList Users { get; set; } = []; + public IReadOnlyList Users { get; set; } = []; /// /// Gets or sets a value indicating whether the playlist is public. -- cgit v1.2.3 From c1dbb49315f90bf03445a960eb8eace86f1ea6f2 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 1 Apr 2024 20:43:05 +0200 Subject: Implement update endpoint --- .../Playlists/PlaylistManager.cs | 88 ++++++++++++++-------- Jellyfin.Api/Controllers/PlaylistsController.cs | 74 ++++++++++-------- .../Models/PlaylistDtos/UpdatePlaylistDto.cs | 34 +++++++++ .../Migrations/Routines/FixPlaylistOwner.cs | 4 +- .../Playlists/IPlaylistManager.cs | 29 ++++--- MediaBrowser.Controller/Playlists/Playlist.cs | 2 +- .../Parsers/BaseItemXmlParser.cs | 4 +- MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 19 ++--- .../Playlists/PlaylistUpdateRequest.cs | 41 ++++++++++ 9 files changed, 202 insertions(+), 93 deletions(-) create mode 100644 Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs create mode 100644 MediaBrowser.Model/Playlists/PlaylistUpdateRequest.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 0e5add635d..517ec68dce 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -71,56 +71,56 @@ namespace Emby.Server.Implementations.Playlists return GetPlaylistsFolder(userId).GetChildren(user, true).OfType(); } - public async Task CreatePlaylist(PlaylistCreationRequest options) + public async Task CreatePlaylist(PlaylistCreationRequest request) { - var name = options.Name; + var name = request.Name; var folderName = _fileSystem.GetValidFilename(name); - var parentFolder = GetPlaylistsFolder(options.UserId); + var parentFolder = GetPlaylistsFolder(request.UserId); if (parentFolder is null) { throw new ArgumentException(nameof(parentFolder)); } - if (options.MediaType is null || options.MediaType == MediaType.Unknown) + if (request.MediaType is null || request.MediaType == MediaType.Unknown) { - foreach (var itemId in options.ItemIdList) + foreach (var itemId in request.ItemIdList) { var item = _libraryManager.GetItemById(itemId) ?? throw new ArgumentException("No item exists with the supplied Id"); if (item.MediaType != MediaType.Unknown) { - options.MediaType = item.MediaType; + request.MediaType = item.MediaType; } else if (item is MusicArtist || item is MusicAlbum || item is MusicGenre) { - options.MediaType = MediaType.Audio; + request.MediaType = MediaType.Audio; } else if (item is Genre) { - options.MediaType = MediaType.Video; + request.MediaType = MediaType.Video; } else { if (item is Folder folder) { - options.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist) + request.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlaylist) .Select(i => i.MediaType) .FirstOrDefault(i => i != MediaType.Unknown); } } - if (options.MediaType is not null && options.MediaType != MediaType.Unknown) + if (request.MediaType is not null && request.MediaType != MediaType.Unknown) { break; } } } - if (options.MediaType is null || options.MediaType == MediaType.Unknown) + if (request.MediaType is null || request.MediaType == MediaType.Unknown) { - options.MediaType = MediaType.Audio; + request.MediaType = MediaType.Audio; } - var user = _userManager.GetUserById(options.UserId); + var user = _userManager.GetUserById(request.UserId); var path = Path.Combine(parentFolder.Path, folderName); path = GetTargetPath(path); @@ -133,20 +133,20 @@ namespace Emby.Server.Implementations.Playlists { Name = name, Path = path, - OwnerUserId = options.UserId, - Shares = options.Users ?? [], - OpenAccess = options.Public ?? false + OwnerUserId = request.UserId, + Shares = request.Users ?? [], + OpenAccess = request.Public ?? false }; - playlist.SetMediaType(options.MediaType); + playlist.SetMediaType(request.MediaType); parentFolder.AddChild(playlist); await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None) .ConfigureAwait(false); - if (options.ItemIdList.Count > 0) + if (request.ItemIdList.Count > 0) { - await AddToPlaylistInternal(playlist.Id, options.ItemIdList, user, new DtoOptions(false) + await AddToPlaylistInternal(playlist.Id, request.ItemIdList, user, new DtoOptions(false) { EnableImages = true }).ConfigureAwait(false); @@ -233,7 +233,7 @@ namespace Emby.Server.Implementations.Playlists // Update the playlist in the repository playlist.LinkedChildren = newLinkedChildren; - await UpdatePlaylist(playlist).ConfigureAwait(false); + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); // Refresh playlist metadata _providerManager.QueueRefresh( @@ -262,7 +262,7 @@ namespace Emby.Server.Implementations.Playlists .Select(i => i.Item1) .ToArray(); - await UpdatePlaylist(playlist).ConfigureAwait(false); + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); _providerManager.QueueRefresh( playlist.Id, @@ -306,7 +306,7 @@ namespace Emby.Server.Implementations.Playlists playlist.LinkedChildren = [.. newList]; - await UpdatePlaylist(playlist).ConfigureAwait(false); + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); } /// @@ -530,7 +530,7 @@ namespace Emby.Server.Implementations.Playlists { playlist.OwnerUserId = rankedShares[0].UserId; playlist.Shares = rankedShares.Skip(1).ToArray(); - await UpdatePlaylist(playlist).ConfigureAwait(false); + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); } else if (!playlist.OpenAccess) { @@ -548,12 +548,40 @@ namespace Emby.Server.Implementations.Playlists } } - public async Task ToggleOpenAccess(Guid playlistId, Guid userId) + public async Task UpdatePlaylist(PlaylistUpdateRequest request) { - var playlist = GetPlaylist(userId, playlistId); - playlist.OpenAccess = !playlist.OpenAccess; + var playlist = GetPlaylist(request.UserId, request.Id); + + if (request.Ids is not null) + { + playlist.LinkedChildren = []; + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); + + var user = _userManager.GetUserById(request.UserId); + await AddToPlaylistInternal(request.Id, request.Ids, user, new DtoOptions(false) + { + EnableImages = true + }).ConfigureAwait(false); + + playlist = GetPlaylist(request.UserId, request.Id); + } + + if (request.Name is not null) + { + playlist.Name = request.Name; + } + + if (request.Users is not null) + { + playlist.Shares = request.Users; + } + + if (request.Public is not null) + { + playlist.OpenAccess = request.Public.Value; + } - await UpdatePlaylist(playlist).ConfigureAwait(false); + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); } public async Task AddToShares(Guid playlistId, Guid userId, PlaylistUserPermissions share) @@ -568,7 +596,7 @@ namespace Emby.Server.Implementations.Playlists shares.Add(share); playlist.Shares = shares; - await UpdatePlaylist(playlist).ConfigureAwait(false); + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); } public async Task RemoveFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share) @@ -577,10 +605,10 @@ namespace Emby.Server.Implementations.Playlists var shares = playlist.Shares.ToList(); shares.Remove(share); playlist.Shares = shares; - await UpdatePlaylist(playlist).ConfigureAwait(false); + await UpdatePlaylistInternal(playlist).ConfigureAwait(false); } - private async Task UpdatePlaylist(Playlist playlist) + private async Task UpdatePlaylistInternal(Playlist playlist) { await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index 862e5235ee..de4c542d94 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -101,72 +101,82 @@ public class PlaylistsController : BaseJellyfinApiController } /// - /// Get a playlist's users. + /// Updates a playlist. /// /// The playlist id. - /// Found shares. + /// The id. + /// Playlist updated. /// Unauthorized access. /// Playlist not found. /// - /// A list of objects. + /// A that represents the asynchronous operation to update a playlist. + /// The task result contains an indicating success. /// - [HttpGet("{playlistId}/User")] + [HttpPost("{playlistId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult> GetPlaylistUsers( - [FromRoute, Required] Guid playlistId) + public async Task UpdatePlaylist( + [FromRoute, Required] Guid playlistId, + [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] UpdatePlaylistDto updatePlaylistRequest) { - var userId = User.GetUserId(); + var callingUserId = User.GetUserId(); - var playlist = _playlistManager.GetPlaylist(userId, playlistId); + var playlist = _playlistManager.GetPlaylist(callingUserId, playlistId); if (playlist is null) { return NotFound("Playlist not found"); } - var isPermitted = playlist.OwnerUserId.Equals(userId) - || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(userId)); + var isPermitted = playlist.OwnerUserId.Equals(callingUserId) + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); - return isPermitted ? playlist.Shares.ToList() : Unauthorized("Unauthorized Access"); + if (!isPermitted) + { + return Unauthorized("Unauthorized access"); + } + + await _playlistManager.UpdatePlaylist(new PlaylistUpdateRequest + { + UserId = callingUserId, + Id = playlistId, + Name = updatePlaylistRequest.Name, + Ids = updatePlaylistRequest.Ids, + Users = updatePlaylistRequest.Users, + Public = updatePlaylistRequest.Public + }).ConfigureAwait(false); + + return NoContent(); } /// - /// Toggles public access of a playlist. + /// Get a playlist's users. /// /// The playlist id. - /// Public access toggled. + /// Found shares. /// Unauthorized access. /// Playlist not found. /// - /// A that represents the asynchronous operation to toggle public access of a playlist. - /// The task result contains an indicating success. + /// A list of objects. /// - [HttpPost("{playlistId}/TogglePublic")] - [ProducesResponseType(StatusCodes.Status204NoContent)] + [HttpGet("{playlistId}/User")] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task TogglePublicAccess( + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult> GetPlaylistUsers( [FromRoute, Required] Guid playlistId) { - var callingUserId = User.GetUserId(); + var userId = User.GetUserId(); - var playlist = _playlistManager.GetPlaylist(callingUserId, playlistId); + var playlist = _playlistManager.GetPlaylist(userId, playlistId); if (playlist is null) { return NotFound("Playlist not found"); } - var isPermitted = playlist.OwnerUserId.Equals(callingUserId) - || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); - - if (!isPermitted) - { - return Unauthorized("Unauthorized access"); - } - - await _playlistManager.ToggleOpenAccess(playlistId, callingUserId).ConfigureAwait(false); + var isPermitted = playlist.OwnerUserId.Equals(userId) + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(userId)); - return NoContent(); + return isPermitted ? playlist.Shares.ToList() : Unauthorized("Unauthorized Access"); } /// @@ -206,7 +216,7 @@ public class PlaylistsController : BaseJellyfinApiController return Unauthorized("Unauthorized access"); } - await _playlistManager.AddToShares(playlistId, callingUserId, new PlaylistUserPermissions(userId.ToString(), canEdit)).ConfigureAwait(false); + await _playlistManager.AddToShares(playlistId, callingUserId, new PlaylistUserPermissions(userId, canEdit)).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs new file mode 100644 index 0000000000..93e544eed8 --- /dev/null +++ b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Model.Entities; + +namespace Jellyfin.Api.Models.PlaylistDtos; + +/// +/// Updateexisting playlist dto. +/// +public class UpdatePlaylistDto +{ + /// + /// Gets or sets the name of the new playlist. + /// + public string? Name { get; set; } + + /// + /// Gets or sets item ids of the playlist. + /// + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList? Ids { get; set; } + + /// + /// Gets or sets the playlist users. + /// + public IReadOnlyList? Users { get; set; } + + /// + /// Gets or sets a value indicating whether the playlist is public. + /// + public bool? Public { get; set; } +} diff --git a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs index 06596c171f..3655a610d3 100644 --- a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs +++ b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs @@ -57,9 +57,9 @@ internal class FixPlaylistOwner : IMigrationRoutine if (shares.Count > 0) { var firstEditShare = shares.First(x => x.CanEdit); - if (firstEditShare is not null && Guid.TryParse(firstEditShare.UserId, out var guid)) + if (firstEditShare is not null) { - playlist.OwnerUserId = guid; + playlist.OwnerUserId = firstEditShare.UserId; playlist.Shares = shares.Where(x => x != firstEditShare).ToArray(); playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); _playlistManager.SavePlaylistFile(playlist); diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs index 1750be6199..464620427a 100644 --- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs +++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs @@ -20,19 +20,25 @@ namespace MediaBrowser.Controller.Playlists Playlist GetPlaylist(Guid userId, Guid playlistId); /// - /// Gets the playlists. + /// Creates the playlist. /// - /// The user identifier. - /// IEnumerable<Playlist>. - IEnumerable GetPlaylists(Guid userId); + /// The . + /// Task<Playlist>. + Task CreatePlaylist(PlaylistCreationRequest request); /// - /// Toggle OpenAccess policy of the playlist. + /// Updates a playlist. /// - /// The playlist identifier. - /// The user identifier. + /// The . /// Task. - Task ToggleOpenAccess(Guid playlistId, Guid userId); + Task UpdatePlaylist(PlaylistUpdateRequest request); + + /// + /// Gets the playlists. + /// + /// The user identifier. + /// IEnumerable<Playlist>. + IEnumerable GetPlaylists(Guid userId); /// /// Adds a share to the playlist. @@ -52,13 +58,6 @@ namespace MediaBrowser.Controller.Playlists /// Task. Task RemoveFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share); - /// - /// Creates the playlist. - /// - /// The options. - /// Task<Playlist>. - Task CreatePlaylist(PlaylistCreationRequest options); - /// /// Adds to playlist. /// diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index b948d2e18b..747dd9f637 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -252,7 +252,7 @@ namespace MediaBrowser.Controller.Playlists return false; } - return shares.Any(share => Guid.TryParse(share.UserId, out var id) && id.Equals(userId)); + return shares.Any(s => s.UserId.Equals(userId)); } public override bool IsVisibleStandalone(User user) diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 22ae3f12b2..a7e027d94a 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -862,9 +862,9 @@ namespace MediaBrowser.LocalMetadata.Parsers } // This is valid - if (!string.IsNullOrWhiteSpace(userId)) + if (!string.IsNullOrWhiteSpace(userId) && Guid.TryParse(userId, out var guid)) { - return new PlaylistUserPermissions(userId, canEdit); + return new PlaylistUserPermissions(guid, canEdit); } return null; diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 5a71930799..ee0d10bea9 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -420,19 +420,16 @@ namespace MediaBrowser.LocalMetadata.Savers foreach (var share in item.Shares) { - if (share.UserId is not null) - { - await writer.WriteStartElementAsync(null, "Share", null).ConfigureAwait(false); + await writer.WriteStartElementAsync(null, "Share", null).ConfigureAwait(false); - await writer.WriteElementStringAsync(null, "UserId", null, share.UserId).ConfigureAwait(false); - await writer.WriteElementStringAsync( - null, - "CanEdit", - null, - share.CanEdit.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()).ConfigureAwait(false); + await writer.WriteElementStringAsync(null, "UserId", null, share.UserId.ToString()).ConfigureAwait(false); + await writer.WriteElementStringAsync( + null, + "CanEdit", + null, + share.CanEdit.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()).ConfigureAwait(false); - await writer.WriteEndElementAsync().ConfigureAwait(false); - } + await writer.WriteEndElementAsync().ConfigureAwait(false); } await writer.WriteEndElementAsync().ConfigureAwait(false); diff --git a/MediaBrowser.Model/Playlists/PlaylistUpdateRequest.cs b/MediaBrowser.Model/Playlists/PlaylistUpdateRequest.cs new file mode 100644 index 0000000000..f574e679c3 --- /dev/null +++ b/MediaBrowser.Model/Playlists/PlaylistUpdateRequest.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Model.Playlists; + +/// +/// A playlist creation request. +/// +public class PlaylistUpdateRequest +{ + /// + /// Gets or sets the id of the playlist. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets the id of the user updating the playlist. + /// + public Guid UserId { get; set; } + + /// + /// Gets or sets the name of the playlist. + /// + public string? Name { get; set; } + + /// + /// Gets or sets item ids to add to the playlist. + /// + public IReadOnlyList? Ids { get; set; } + + /// + /// Gets or sets the playlist users. + /// + public IReadOnlyList? Users { get; set; } + + /// + /// Gets or sets a value indicating whether the playlist is public. + /// + public bool? Public { get; set; } +} -- cgit v1.2.3 From ec36aaa73a776b28e8c311a7b049bf12d41761c8 Mon Sep 17 00:00:00 2001 From: Bas <44002186+854562@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:59:04 +0000 Subject: Translated using Weblate (Dutch) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/ --- Emby.Server.Implementations/Localization/Core/nl.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index a925b71345..894d4b8ea9 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -126,5 +126,7 @@ "External": "Extern", "HearingImpaired": "Slechthorend", "TaskRefreshTrickplayImages": "Trickplay-afbeeldingen genereren", - "TaskRefreshTrickplayImagesDescription": "Creëert trickplay-voorvertoningen voor video's in bibliotheken waarvoor dit is ingeschakeld." + "TaskRefreshTrickplayImagesDescription": "Creëert trickplay-voorvertoningen voor video's in bibliotheken waarvoor dit is ingeschakeld.", + "TaskCleanCollectionsAndPlaylists": "Collecties en afspeellijsten opruimen", + "TaskCleanCollectionsAndPlaylistsDescription": "Verwijdert niet langer bestaande items uit collecties en afspeellijsten." } -- cgit v1.2.3 From 8cf77424f6c432386fb06017eccfa0e9f880a3ba Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 2 Apr 2024 08:08:36 +0200 Subject: Apply review suggestions --- .../Playlists/PlaylistManager.cs | 24 +++++++++-------- Jellyfin.Api/Controllers/PlaylistsController.cs | 31 +++++++++++++--------- .../Models/PlaylistDtos/UpdatePlaylistDto.cs | 2 +- .../Models/PlaylistDtos/UpdatePlaylistUserDto.cs | 12 +++++++++ .../Playlists/IPlaylistManager.cs | 18 ++++++------- .../Playlists/PlaylistUpdateRequest.cs | 2 +- .../Playlists/PlaylistUserUpdateRequest.cs | 24 +++++++++++++++++ 7 files changed, 77 insertions(+), 36 deletions(-) create mode 100644 Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistUserDto.cs create mode 100644 MediaBrowser.Model/Playlists/PlaylistUserUpdateRequest.cs (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 517ec68dce..7a6cf9efff 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -22,6 +22,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Playlists; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using PlaylistsNET.Content; @@ -59,7 +60,7 @@ namespace Emby.Server.Implementations.Playlists _appConfig = appConfig; } - public Playlist GetPlaylist(Guid userId, Guid playlistId) + public Playlist GetPlaylistForUser(Guid playlistId, Guid userId) { return GetPlaylists(userId).Where(p => p.Id.Equals(playlistId)).FirstOrDefault(); } @@ -178,7 +179,7 @@ namespace Emby.Server.Implementations.Playlists return Playlist.GetPlaylistItems(playlistMediaType, items, user, options); } - public Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection itemIds, Guid userId) + public Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection itemIds, Guid userId) { var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); @@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.Playlists RefreshPriority.High); } - public async Task RemoveFromPlaylistAsync(string playlistId, IEnumerable entryIds) + public async Task RemoveItemFromPlaylistAsync(string playlistId, IEnumerable entryIds) { if (_libraryManager.GetItemById(playlistId) is not Playlist playlist) { @@ -550,7 +551,7 @@ namespace Emby.Server.Implementations.Playlists public async Task UpdatePlaylist(PlaylistUpdateRequest request) { - var playlist = GetPlaylist(request.UserId, request.Id); + var playlist = GetPlaylistForUser(request.Id, request.UserId); if (request.Ids is not null) { @@ -563,7 +564,7 @@ namespace Emby.Server.Implementations.Playlists EnableImages = true }).ConfigureAwait(false); - playlist = GetPlaylist(request.UserId, request.Id); + playlist = GetPlaylistForUser(request.Id, request.UserId); } if (request.Name is not null) @@ -584,24 +585,25 @@ namespace Emby.Server.Implementations.Playlists await UpdatePlaylistInternal(playlist).ConfigureAwait(false); } - public async Task AddToShares(Guid playlistId, Guid userId, PlaylistUserPermissions share) + public async Task AddUserToShares(PlaylistUserUpdateRequest request) { - var playlist = GetPlaylist(userId, playlistId); + var userId = request.UserId; + var playlist = GetPlaylistForUser(request.Id, userId); var shares = playlist.Shares.ToList(); - var existingUserShare = shares.FirstOrDefault(s => s.UserId.Equals(share.UserId)); + var existingUserShare = shares.FirstOrDefault(s => s.UserId.Equals(userId)); if (existingUserShare is not null) { shares.Remove(existingUserShare); } - shares.Add(share); + shares.Add(new PlaylistUserPermissions(userId, request.CanEdit ?? false)); playlist.Shares = shares; await UpdatePlaylistInternal(playlist).ConfigureAwait(false); } - public async Task RemoveFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share) + public async Task RemoveUserFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share) { - var playlist = GetPlaylist(userId, playlistId); + var playlist = GetPlaylistForUser(playlistId, userId); var shares = playlist.Shares.ToList(); shares.Remove(share); playlist.Shares = shares; diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index de4c542d94..12186e02e6 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -121,7 +121,7 @@ public class PlaylistsController : BaseJellyfinApiController { var callingUserId = User.GetUserId(); - var playlist = _playlistManager.GetPlaylist(callingUserId, playlistId); + var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId); if (playlist is null) { return NotFound("Playlist not found"); @@ -167,7 +167,7 @@ public class PlaylistsController : BaseJellyfinApiController { var userId = User.GetUserId(); - var playlist = _playlistManager.GetPlaylist(userId, playlistId); + var playlist = _playlistManager.GetPlaylistForUser(playlistId, userId); if (playlist is null) { return NotFound("Playlist not found"); @@ -184,7 +184,7 @@ public class PlaylistsController : BaseJellyfinApiController /// /// The playlist id. /// The user id. - /// Edit permission. + /// The . /// User's permissions modified. /// Unauthorized access. /// Playlist not found. @@ -195,14 +195,14 @@ public class PlaylistsController : BaseJellyfinApiController [HttpPost("{playlistId}/User/{userId}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task ModifyPlaylistUserPermissions( + public async Task UpdatePlaylistUser( [FromRoute, Required] Guid playlistId, [FromRoute, Required] Guid userId, - [FromBody] bool canEdit) + [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] UpdatePlaylistUserDto updatePlaylistUserRequest) { var callingUserId = User.GetUserId(); - var playlist = _playlistManager.GetPlaylist(callingUserId, playlistId); + var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId); if (playlist is null) { return NotFound("Playlist not found"); @@ -216,7 +216,12 @@ public class PlaylistsController : BaseJellyfinApiController return Unauthorized("Unauthorized access"); } - await _playlistManager.AddToShares(playlistId, callingUserId, new PlaylistUserPermissions(userId, canEdit)).ConfigureAwait(false); + await _playlistManager.AddUserToShares(new PlaylistUserUpdateRequest + { + Id = playlistId, + UserId = userId, + CanEdit = updatePlaylistUserRequest.CanEdit + }).ConfigureAwait(false); return NoContent(); } @@ -243,7 +248,7 @@ public class PlaylistsController : BaseJellyfinApiController { var callingUserId = User.GetUserId(); - var playlist = _playlistManager.GetPlaylist(callingUserId, playlistId); + var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId); if (playlist is null) { return NotFound("Playlist not found"); @@ -263,7 +268,7 @@ public class PlaylistsController : BaseJellyfinApiController return NotFound("User permissions not found"); } - await _playlistManager.RemoveFromShares(playlistId, callingUserId, share).ConfigureAwait(false); + await _playlistManager.RemoveUserFromShares(playlistId, callingUserId, share).ConfigureAwait(false); return NoContent(); } @@ -278,13 +283,13 @@ public class PlaylistsController : BaseJellyfinApiController /// An on success. [HttpPost("{playlistId}/Items")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public async Task AddToPlaylist( + public async Task AddItemToPlaylist( [FromRoute, Required] Guid playlistId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids, [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); - await _playlistManager.AddToPlaylistAsync(playlistId, ids, userId.Value).ConfigureAwait(false); + await _playlistManager.AddItemToPlaylistAsync(playlistId, ids, userId.Value).ConfigureAwait(false); return NoContent(); } @@ -316,11 +321,11 @@ public class PlaylistsController : BaseJellyfinApiController /// An on success. [HttpDelete("{playlistId}/Items")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public async Task RemoveFromPlaylist( + public async Task RemoveItemFromPlaylist( [FromRoute, Required] string playlistId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] entryIds) { - await _playlistManager.RemoveFromPlaylistAsync(playlistId, entryIds).ConfigureAwait(false); + await _playlistManager.RemoveItemFromPlaylistAsync(playlistId, entryIds).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs index 93e544eed8..0e109db3ee 100644 --- a/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs +++ b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Entities; namespace Jellyfin.Api.Models.PlaylistDtos; /// -/// Updateexisting playlist dto. +/// Update existing playlist dto. Fields set to `null` will not be updated and keep their current values. /// public class UpdatePlaylistDto { diff --git a/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistUserDto.cs b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistUserDto.cs new file mode 100644 index 0000000000..60467b5e70 --- /dev/null +++ b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistUserDto.cs @@ -0,0 +1,12 @@ +namespace Jellyfin.Api.Models.PlaylistDtos; + +/// +/// Update existing playlist user dto. Fields set to `null` will not be updated and keep their current values. +/// +public class UpdatePlaylistUserDto +{ + /// + /// Gets or sets a value indicating whether the user can edit the playlist. + /// + public bool? CanEdit { get; set; } +} diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs index 464620427a..821b901a03 100644 --- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs +++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs @@ -14,10 +14,10 @@ namespace MediaBrowser.Controller.Playlists /// /// Gets the playlist. /// - /// The user identifier. /// The playlist identifier. + /// The user identifier. /// Playlist. - Playlist GetPlaylist(Guid userId, Guid playlistId); + Playlist GetPlaylistForUser(Guid playlistId, Guid userId); /// /// Creates the playlist. @@ -43,20 +43,18 @@ namespace MediaBrowser.Controller.Playlists /// /// Adds a share to the playlist. /// - /// The playlist identifier. - /// The user identifier. - /// The share. + /// The . /// Task. - Task AddToShares(Guid playlistId, Guid userId, PlaylistUserPermissions share); + Task AddUserToShares(PlaylistUserUpdateRequest request); /// - /// Rremoves a share from the playlist. + /// Removes a share from the playlist. /// /// The playlist identifier. /// The user identifier. /// The share. /// Task. - Task RemoveFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share); + Task RemoveUserFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share); /// /// Adds to playlist. @@ -65,7 +63,7 @@ namespace MediaBrowser.Controller.Playlists /// The item ids. /// The user identifier. /// Task. - Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection itemIds, Guid userId); + Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection itemIds, Guid userId); /// /// Removes from playlist. @@ -73,7 +71,7 @@ namespace MediaBrowser.Controller.Playlists /// The playlist identifier. /// The entry ids. /// Task. - Task RemoveFromPlaylistAsync(string playlistId, IEnumerable entryIds); + Task RemoveItemFromPlaylistAsync(string playlistId, IEnumerable entryIds); /// /// Gets the playlists folder. diff --git a/MediaBrowser.Model/Playlists/PlaylistUpdateRequest.cs b/MediaBrowser.Model/Playlists/PlaylistUpdateRequest.cs index f574e679c3..db290bbdbf 100644 --- a/MediaBrowser.Model/Playlists/PlaylistUpdateRequest.cs +++ b/MediaBrowser.Model/Playlists/PlaylistUpdateRequest.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Playlists; /// -/// A playlist creation request. +/// A playlist update request. /// public class PlaylistUpdateRequest { diff --git a/MediaBrowser.Model/Playlists/PlaylistUserUpdateRequest.cs b/MediaBrowser.Model/Playlists/PlaylistUserUpdateRequest.cs new file mode 100644 index 0000000000..1840efdf37 --- /dev/null +++ b/MediaBrowser.Model/Playlists/PlaylistUserUpdateRequest.cs @@ -0,0 +1,24 @@ +using System; + +namespace MediaBrowser.Model.Playlists; + +/// +/// A playlist user update request. +/// +public class PlaylistUserUpdateRequest +{ + /// + /// Gets or sets the id of the playlist. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets the id of the updated user. + /// + public Guid UserId { get; set; } + + /// + /// Gets or sets a value indicating whether the user can edit the playlist. + /// + public bool? CanEdit { get; set; } +} -- cgit v1.2.3 From 85cf91c4cf9eeef38dcc5f4705d74dd59116d293 Mon Sep 17 00:00:00 2001 From: Lukáš Kucharczyk Date: Tue, 2 Apr 2024 05:36:58 +0000 Subject: Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/ --- Emby.Server.Implementations/Localization/Core/cs.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 1c7bc75b5c..2fa1c19e3a 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -126,5 +126,7 @@ "External": "Externí", "HearingImpaired": "Sluchově postižení", "TaskRefreshTrickplayImages": "Generovat obrázky pro Trickplay", - "TaskRefreshTrickplayImagesDescription": "Obrázky Trickplay se používají k zobrazení náhledů u videí v knihovnách, kde je to povoleno." + "TaskRefreshTrickplayImagesDescription": "Obrázky Trickplay se používají k zobrazení náhledů u videí v knihovnách, kde je to povoleno.", + "TaskCleanCollectionsAndPlaylists": "Pročistit kolekce a seznamy přehrávání", + "TaskCleanCollectionsAndPlaylistsDescription": "Odstraní neexistující položky z kolekcí a seznamů přehrávání." } -- cgit v1.2.3 From 00499fa27bb61e2ac0e2e932f8406d27b020a08a Mon Sep 17 00:00:00 2001 From: Troja Date: Tue, 2 Apr 2024 05:50:57 +0000 Subject: Translated using Weblate (Belarusian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/be/ --- Emby.Server.Implementations/Localization/Core/be.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/be.json b/Emby.Server.Implementations/Localization/Core/be.json index 05af8d8a5a..77643505e1 100644 --- a/Emby.Server.Implementations/Localization/Core/be.json +++ b/Emby.Server.Implementations/Localization/Core/be.json @@ -125,5 +125,7 @@ "TaskDownloadMissingSubtitles": "Спампаваць адсутныя субтытры", "TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных спісаў прайгравання HLS. Гэта задача можа працаваць у працягу доўгага часу.", "TaskRefreshTrickplayImages": "Стварыце выявы Trickplay", - "TaskRefreshTrickplayImagesDescription": "Стварае прагляд відэаролікаў для Trickplay у падключаных бібліятэках." + "TaskRefreshTrickplayImagesDescription": "Стварае прагляд відэаролікаў для Trickplay у падключаных бібліятэках.", + "TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і спісы прайгравання", + "TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і спісаў прайгравання, якія больш не існуюць." } -- cgit v1.2.3 From e53650fbb5256e0f234c1af1b7dfdbd06e619d85 Mon Sep 17 00:00:00 2001 From: myrad2267 Date: Tue, 2 Apr 2024 10:32:36 +0000 Subject: Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index d04a79de18..db83d4b47d 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -126,5 +126,7 @@ "External": "Externe", "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." + "TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées.", + "TaskCleanCollectionsAndPlaylists": "Nettoyer les collections et les listes de lecture", + "TaskCleanCollectionsAndPlaylistsDescription": "Supprime les éléments des collections et des listes de lecture qui n'existent plus." } -- cgit v1.2.3 From ab4dd6e58296a5952bb4ee8681860b6039bc8567 Mon Sep 17 00:00:00 2001 From: stanol Date: Tue, 2 Apr 2024 11:32:21 +0000 Subject: Translated using Weblate (Ukrainian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/uk/ --- Emby.Server.Implementations/Localization/Core/uk.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json index 3f7fca427b..5f97d1ef95 100644 --- a/Emby.Server.Implementations/Localization/Core/uk.json +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -125,5 +125,7 @@ "External": "Зовнішній", "HearingImpaired": "З порушеннями слуху", "TaskRefreshTrickplayImagesDescription": "Створює trickplay-зображення для відео у ввімкнених медіатеках.", - "TaskRefreshTrickplayImages": "Створити Trickplay-зображення" + "TaskRefreshTrickplayImages": "Створити Trickplay-зображення", + "TaskCleanCollectionsAndPlaylists": "Очистити колекції і списки відтворення", + "TaskCleanCollectionsAndPlaylistsDescription": "Видаляє елементи з колекцій і списків відтворення, які більше не існують." } -- cgit v1.2.3 From 5179c7d158da52f8857599b398bc58e3760d846a Mon Sep 17 00:00:00 2001 From: nextlooper42 Date: Tue, 2 Apr 2024 15:08:53 +0000 Subject: Translated using Weblate (Slovak) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sk/ --- Emby.Server.Implementations/Localization/Core/sk.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index 43594a42eb..905dba5ab0 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -126,5 +126,7 @@ "External": "Externé", "HearingImpaired": "Sluchovo postihnutí", "TaskRefreshTrickplayImages": "Generovanie obrázkov Trickplay", - "TaskRefreshTrickplayImagesDescription": "Vytvára trickplay náhľady pre videá v povolených knižniciach." + "TaskRefreshTrickplayImagesDescription": "Vytvára trickplay náhľady pre videá v povolených knižniciach.", + "TaskCleanCollectionsAndPlaylists": "Vyčistiť kolekcie a playlisty", + "TaskCleanCollectionsAndPlaylistsDescription": "Odstráni položky z kolekcií a playlistov, ktoré už neexistujú." } -- cgit v1.2.3 From 286232e21cd5f7644d940a180e9167d14546b912 Mon Sep 17 00:00:00 2001 From: Kityn Date: Tue, 2 Apr 2024 18:09:04 +0000 Subject: Translated using Weblate (Polish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pl/ --- Emby.Server.Implementations/Localization/Core/pl.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index bd572b744b..64427b459d 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -126,5 +126,7 @@ "TaskKeyframeExtractor": "Ekstraktor klatek kluczowych", "HearingImpaired": "Niedosłyszący", "TaskRefreshTrickplayImages": "Generuj obrazy trickplay", - "TaskRefreshTrickplayImagesDescription": "Tworzy podglądy trickplay dla filmów we włączonych bibliotekach." + "TaskRefreshTrickplayImagesDescription": "Tworzy podglądy trickplay dla filmów we włączonych bibliotekach.", + "TaskCleanCollectionsAndPlaylistsDescription": "Usuwa elementy z kolekcji i list odtwarzania, które już nie istnieją.", + "TaskCleanCollectionsAndPlaylists": "Oczyść kolekcje i listy odtwarzania" } -- cgit v1.2.3 From 4a9565ab52c19be8798e622a3d238c3238b01c07 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Wed, 3 Apr 2024 14:56:56 +0200 Subject: Fix some spelling mistakes --- .../Library/MediaSourceManager.cs | 23 +++++++++++----------- .../Library/MediaStreamSelector.cs | 6 +++--- .../Library/IMediaSourceManager.cs | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 18ada6aeb5..9658bd5665 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.Library if (user is not null) { - SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); + SetDefaultAudioAndSubtitleStreamIndices(item, source, user); if (item.MediaType == MediaType.Audio) { @@ -296,7 +296,7 @@ namespace Emby.Server.Implementations.Library catch (Exception ex) { _logger.LogError(ex, "Error getting media sources"); - return Enumerable.Empty(); + return []; } } @@ -339,7 +339,7 @@ namespace Emby.Server.Implementations.Library { foreach (var source in sources) { - SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); + SetDefaultAudioAndSubtitleStreamIndices(item, source, user); if (item.MediaType == MediaType.Audio) { @@ -360,7 +360,7 @@ namespace Emby.Server.Implementations.Library { if (string.IsNullOrEmpty(language)) { - return Array.Empty(); + return []; } var culture = _localizationManager.FindLanguageInfo(language); @@ -369,14 +369,15 @@ namespace Emby.Server.Implementations.Library return culture.ThreeLetterISOLanguageNames; } - return new string[] { language }; + return [language]; } private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) { if (userData.SubtitleStreamIndex.HasValue && user.RememberSubtitleSelections - && user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection) + && user.SubtitleMode != SubtitlePlaybackMode.None + && allowRememberingSelection) { var index = userData.SubtitleStreamIndex.Value; // Make sure the saved index is still valid @@ -390,7 +391,7 @@ namespace Emby.Server.Implementations.Library var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; - var audioLangage = defaultAudioIndex is null + var audioLanguage = defaultAudioIndex is null ? null : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); @@ -398,9 +399,9 @@ namespace Emby.Server.Implementations.Library source.MediaStreams, preferredSubs, user.SubtitleMode, - audioLangage); + audioLanguage); - MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage); + MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLanguage); } private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) @@ -421,7 +422,7 @@ namespace Emby.Server.Implementations.Library source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack); } - public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) + public void SetDefaultAudioAndSubtitleStreamIndices(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?.MediaType ?? MediaType.Video; @@ -526,7 +527,7 @@ namespace Emby.Server.Implementations.Library var item = request.ItemId.IsEmpty() ? null : _libraryManager.GetItemById(request.ItemId); - SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); + SetDefaultAudioAndSubtitleStreamIndices(item, clone, user); } return new Tuple(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider); diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 6aef87c525..ea223e3ece 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -124,16 +124,16 @@ namespace Emby.Server.Implementations.Library } else if (mode == SubtitlePlaybackMode.Always) { - // always load the most suitable full subtitles + // Always load the most suitable full subtitles filteredStreams = sortedStreams.Where(s => !s.IsForced).ToList(); } else if (mode == SubtitlePlaybackMode.OnlyForced) { - // always load the most suitable full subtitles + // Always load the most suitable full subtitles filteredStreams = sortedStreams.Where(s => s.IsForced).ToList(); } - // load forced subs if we have found no suitable full subtitles + // Load forced subs if we have found no suitable full subtitles var iterStreams = filteredStreams is null || filteredStreams.Count == 0 ? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) : filteredStreams; diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index bace703ada..44a1a85e30 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -138,7 +138,7 @@ namespace MediaBrowser.Controller.Library MediaProtocol GetPathProtocol(string path); - void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user); + void SetDefaultAudioAndSubtitleStreamIndices(BaseItem item, MediaSourceInfo source, User user); Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProbeDelay, bool isLiveStream, CancellationToken cancellationToken); } -- cgit v1.2.3 From 2428672599763f47d9ac0fc74d9e777e40f7345b Mon Sep 17 00:00:00 2001 From: queeup Date: Wed, 3 Apr 2024 12:54:02 +0000 Subject: Translated using Weblate (Turkish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/ --- Emby.Server.Implementations/Localization/Core/tr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index d7a627d127..0597539578 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -126,5 +126,7 @@ "External": "Harici", "HearingImpaired": "Duyma Engelli", "TaskRefreshTrickplayImages": "Trickplay Görselleri Oluştur", - "TaskRefreshTrickplayImagesDescription": "Etkin kütüphanelerdeki videolar için trickplay önizlemeleri oluşturur." + "TaskRefreshTrickplayImagesDescription": "Etkin kütüphanelerdeki videolar için trickplay önizlemeleri oluşturur.", + "TaskCleanCollectionsAndPlaylistsDescription": "Artık var olmayan koleksiyon ve çalma listelerindeki ögeleri kaldırır.", + "TaskCleanCollectionsAndPlaylists": "Koleksiyonları ve çalma listelerini temizleyin" } -- cgit v1.2.3 From 5eadf6f4cff1dcd607568a7e474fd7ef1b35f482 Mon Sep 17 00:00:00 2001 From: Kristijonas Kuzmickas Date: Thu, 4 Apr 2024 08:06:13 +0000 Subject: Translated using Weblate (Lithuanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/ --- Emby.Server.Implementations/Localization/Core/lt-LT.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index e7279994bb..004ce68f58 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -126,5 +126,7 @@ "External": "Išorinis", "HearingImpaired": "Su klausos sutrikimais", "TaskRefreshTrickplayImages": "Generuoti Trickplay atvaizdus", - "TaskRefreshTrickplayImagesDescription": "Sukuria trickplay peržiūras vaizdo įrašams įgalintose bibliotekose." + "TaskRefreshTrickplayImagesDescription": "Sukuria trickplay peržiūras vaizdo įrašams įgalintose bibliotekose.", + "TaskCleanCollectionsAndPlaylists": "Sutvarko duomenis jūsų kolekcijose ir grojaraščiuose.", + "TaskCleanCollectionsAndPlaylistsDescription": "Pašalina nebeegzistuojančius elementus iš kolekcijų ir grojaraščių." } -- cgit v1.2.3 From c87c26d33acbe63b623182112bf4ec6ddf0d2bbe Mon Sep 17 00:00:00 2001 From: Tim Zschuppe Date: Thu, 4 Apr 2024 19:02:51 +0000 Subject: Translated using Weblate (German) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/de/ --- Emby.Server.Implementations/Localization/Core/de.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 7a4c2067ba..d8b2f828f6 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -126,5 +126,7 @@ "External": "Extern", "HearingImpaired": "Hörgeschädigt", "TaskRefreshTrickplayImages": "Trickplay-Bilder generieren", - "TaskRefreshTrickplayImagesDescription": "Erstellt eine Trickplay-Vorschau für Videos in aktivierten Bibliotheken." + "TaskRefreshTrickplayImagesDescription": "Erstellt eine Trickplay-Vorschau für Videos in aktivierten Bibliotheken.", + "TaskCleanCollectionsAndPlaylists": "Sammlungen und Playlisten aufräumen", + "TaskCleanCollectionsAndPlaylistsDescription": "Lösche nicht mehr vorhandene Einträge aus den Sammlungen und Playlisten." } -- cgit v1.2.3 From 3a8e65893283d3f8759170ce72b050e0e632b280 Mon Sep 17 00:00:00 2001 From: Евгений Владимирович Инякин Date: Sun, 7 Apr 2024 11:51:38 +0000 Subject: Translated using Weblate (Russian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ru/ --- Emby.Server.Implementations/Localization/Core/ru.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 26d678a0c3..3d3f88709b 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -126,5 +126,7 @@ "External": "Внешние", "HearingImpaired": "Для слабослышащих", "TaskRefreshTrickplayImages": "Сгенерировать изображения для Trickplay", - "TaskRefreshTrickplayImagesDescription": "Создает предпросмотры для Trickplay для видео в библиотеках, где эта функция включена." + "TaskRefreshTrickplayImagesDescription": "Создает предпросмотры для Trickplay для видео в библиотеках, где эта функция включена.", + "TaskCleanCollectionsAndPlaylists": "Очистка коллекций и списков воспроизведения", + "TaskCleanCollectionsAndPlaylistsDescription": "Удаляет элементы из коллекций и списков воспроизведения, которые больше не существуют." } -- cgit v1.2.3 From f62671dc3fc4c5edf76887120949ef9ce604dd01 Mon Sep 17 00:00:00 2001 From: bene toffix Date: Mon, 8 Apr 2024 16:01:46 +0000 Subject: Translated using Weblate (Catalan) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/ --- Emby.Server.Implementations/Localization/Core/ca.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index c4d8c69479..b7633f77c3 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -126,5 +126,7 @@ "External": "Extern", "HearingImpaired": "Discapacitat auditiva", "TaskRefreshTrickplayImages": "Generar miniatures de línia de temps", - "TaskRefreshTrickplayImagesDescription": "Crear miniatures de línia de temps per vídeos en les biblioteques habilitades." + "TaskRefreshTrickplayImagesDescription": "Crear miniatures de línia de temps per vídeos en les biblioteques habilitades.", + "TaskCleanCollectionsAndPlaylistsDescription": "Esborra elements de col·leccions i llistes de reproducció que ja no existeixen.", + "TaskCleanCollectionsAndPlaylists": "Neteja col·leccions i llistes de reproducció" } -- cgit v1.2.3 From acf77169a0b1c750f3dd908df1487c5b88c5ef02 Mon Sep 17 00:00:00 2001 From: milo !! Date: Mon, 8 Apr 2024 11:26:14 +0000 Subject: Translated using Weblate (English (United Kingdom)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en_GB/ --- Emby.Server.Implementations/Localization/Core/en-GB.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index 32bf893100..ff0c3d23df 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -126,5 +126,7 @@ "External": "External", "HearingImpaired": "Hearing Impaired", "TaskRefreshTrickplayImages": "Generate Trickplay Images", - "TaskRefreshTrickplayImagesDescription": "Creates trickplay previews for videos in enabled libraries." + "TaskRefreshTrickplayImagesDescription": "Creates trickplay previews for videos in enabled libraries.", + "TaskCleanCollectionsAndPlaylists": "Clean up collections and playlists", + "TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist." } -- cgit v1.2.3 From e6873de7daa1782a621892f13c844f59785aee01 Mon Sep 17 00:00:00 2001 From: Blackspirits Date: Tue, 9 Apr 2024 11:07:27 +0000 Subject: Translated using Weblate (Portuguese (Portugal)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/ --- Emby.Server.Implementations/Localization/Core/pt-PT.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index 92ac2681e4..dc96088ff5 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -126,5 +126,7 @@ "External": "Externo", "HearingImpaired": "Surdo", "TaskRefreshTrickplayImages": "Gerar imagens de truques", - "TaskRefreshTrickplayImagesDescription": "Cria vizualizações de truques para videos nas librarias ativas." + "TaskRefreshTrickplayImagesDescription": "Cria vizualizações de truques para videos nas librarias ativas.", + "TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.", + "TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução" } -- cgit v1.2.3 From 63d7d84d2c3338155511cafc44c64084649bf02a Mon Sep 17 00:00:00 2001 From: Blackspirits Date: Tue, 9 Apr 2024 11:07:20 +0000 Subject: Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/ --- Emby.Server.Implementations/Localization/Core/pt.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index 103393a1e4..de487488e3 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -125,5 +125,7 @@ "TaskKeyframeExtractor": "Extrator de quadro-chave", "TaskKeyframeExtractorDescription": "Retira frames chave do video para criar listas HLS precisas. Esta tarefa pode correr durante algum tempo.", "TaskRefreshTrickplayImages": "Gerar miniaturas de vídeo", - "TaskRefreshTrickplayImagesDescription": "Cria miniaturas de vídeo para vídeos nas bibliotecas definidas." + "TaskRefreshTrickplayImagesDescription": "Cria miniaturas de vídeo para vídeos nas bibliotecas definidas.", + "TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.", + "TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução" } -- cgit v1.2.3 From f7f7ba885383d164239976a79e8af4b698aa8bdd Mon Sep 17 00:00:00 2001 From: ViggoC Date: Wed, 10 Apr 2024 16:07:57 +0000 Subject: Translated using Weblate (Chinese (Simplified)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index b88d4eeaf5..1f1458b6c0 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -126,5 +126,7 @@ "External": "外部", "HearingImpaired": "听力障碍", "TaskRefreshTrickplayImages": "生成时间轴缩略图", - "TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成时间轴缩略图。" + "TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成时间轴缩略图。", + "TaskCleanCollectionsAndPlaylists": "清理合集和播放列表", + "TaskCleanCollectionsAndPlaylistsDescription": "清理合集和播放列表中已不存在的项目。" } -- cgit v1.2.3 From 0c36f539ec23129758501613216b9898e6286371 Mon Sep 17 00:00:00 2001 From: Dan Johansen Date: Thu, 11 Apr 2024 10:33:04 +0000 Subject: Translated using Weblate (Danish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/da/ --- Emby.Server.Implementations/Localization/Core/da.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 092af34b6b..b5e2c9b6b8 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -126,5 +126,7 @@ "External": "Ekstern", "HearingImpaired": "Hørehæmmet", "TaskRefreshTrickplayImages": "Generér Trickplay Billeder", - "TaskRefreshTrickplayImagesDescription": "Laver trickplay forhåndsvisninger for videoer i aktiverede biblioteker." + "TaskRefreshTrickplayImagesDescription": "Laver trickplay forhåndsvisninger for videoer i aktiverede biblioteker.", + "TaskCleanCollectionsAndPlaylists": "Ryd op i samlinger og afspilningslister", + "TaskCleanCollectionsAndPlaylistsDescription": "Fjerner enheder fra samlinger og afspilningslister der ikke eksisterer længere." } -- cgit v1.2.3 From 6f1cf595b8d479d37356423f67fd27ed03bd6ba2 Mon Sep 17 00:00:00 2001 From: VitoFe Date: Thu, 11 Apr 2024 16:05:26 +0000 Subject: Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index a34bcc4907..8d8311557e 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -126,5 +126,7 @@ "External": "Esterno", "HearingImpaired": "con problemi di udito", "TaskRefreshTrickplayImages": "Genera immagini Trickplay", - "TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate." + "TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate.", + "TaskCleanCollectionsAndPlaylists": "Ripulire le raccolte e le playlist", + "TaskCleanCollectionsAndPlaylistsDescription": "Rimuove gli elementi dalle raccolte e dalle playlist che non esistono più." } -- cgit v1.2.3 From f8266b3e088b235300920ececf3569ab87e669a5 Mon Sep 17 00:00:00 2001 From: hoanghuy309 Date: Fri, 12 Apr 2024 09:28:56 +0000 Subject: Translated using Weblate (Vietnamese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/ --- Emby.Server.Implementations/Localization/Core/vi.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index e92752c5f7..af9b54ad1e 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -125,5 +125,7 @@ "External": "Bên ngoài", "HearingImpaired": "Khiếm Thính", "TaskRefreshTrickplayImages": "Tạo Ảnh Xem Trước Trickplay", - "TaskRefreshTrickplayImagesDescription": "Tạo bản xem trước trịckplay cho video trong thư viện đã bật." + "TaskRefreshTrickplayImagesDescription": "Tạo bản xem trước trịckplay cho video trong thư viện đã bật.", + "TaskCleanCollectionsAndPlaylists": "Dọn dẹp bộ sưu tập và danh sách phát", + "TaskCleanCollectionsAndPlaylistsDescription": "Xóa các mục khỏi bộ sưu tập và danh sách phát không còn tồn tại." } -- cgit v1.2.3 From d3b9ebfa2ee7917e74863c33170873ae4c3dbc44 Mon Sep 17 00:00:00 2001 From: Mikal Stordal Date: Fri, 12 Apr 2024 22:53:39 +0000 Subject: fix: fix off-by-one error in `GetAttributeValue` Co-authored-by: fearnlj01 --- Emby.Server.Implementations/Library/PathExtensions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index c4b6b37561..21e7079d88 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -31,8 +31,9 @@ namespace Emby.Server.Implementations.Library var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase); - // Must be at least 3 characters after the attribute =, ], any character. - var maxIndex = str.Length - attribute.Length - 3; + // Must be at least 3 characters after the attribute =, ], any character, + // then we offset it by 1, because we want the index and not length. + var maxIndex = str.Length - attribute.Length - 2; while (attributeIndex > -1 && attributeIndex < maxIndex) { var attributeEnd = attributeIndex + attribute.Length; -- cgit v1.2.3 From eccc9a0b647b8cdd7380ea83ddbb77333ce691e3 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 12 Apr 2024 17:44:16 -0600 Subject: Add index for lastPlayedDate (#11342) --- Emby.Server.Implementations/Data/SqliteUserDataRepository.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index a5edcc58c0..20359e4ad7 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -58,7 +58,8 @@ namespace Emby.Server.Implementations.Data "create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)", "create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)", "create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)", - "create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)")); + "create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)", + "create index if not exists UserDatasIndex5 on UserDatas (key, userId, lastPlayedDate)")); if (!userDataTableExists) { -- cgit v1.2.3 From 134bf7a6a58402a08e8e59f7f9ee626881dfa183 Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Sat, 13 Apr 2024 01:44:45 +0200 Subject: Don't throw if file was already removed (#11286) --- .../Library/LibraryManager.cs | 26 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 0c854bdb74..dced6868d7 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -338,7 +338,7 @@ namespace Emby.Server.Implementations.Library if (item is LiveTvProgram) { _logger.LogDebug( - "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + "Removing item, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", item.GetType().Name, item.Name ?? "Unknown name", item.Path ?? string.Empty, @@ -347,7 +347,7 @@ namespace Emby.Server.Implementations.Library else { _logger.LogInformation( - "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + "Removing item, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", item.GetType().Name, item.Name ?? "Unknown name", item.Path ?? string.Empty, @@ -366,7 +366,7 @@ namespace Emby.Server.Implementations.Library } _logger.LogDebug( - "Deleting metadata path, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + "Deleting metadata path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", item.GetType().Name, item.Name ?? "Unknown name", metadataPath, @@ -395,7 +395,7 @@ namespace Emby.Server.Implementations.Library try { _logger.LogInformation( - "Deleting item path, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + "Deleting item path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", item.GetType().Name, item.Name ?? "Unknown name", fileSystemInfo.FullName, @@ -410,6 +410,24 @@ namespace Emby.Server.Implementations.Library File.Delete(fileSystemInfo.FullName); } } + catch (DirectoryNotFoundException) + { + _logger.LogInformation( + "Directory not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + fileSystemInfo.FullName, + item.Id); + } + catch (FileNotFoundException) + { + _logger.LogInformation( + "File not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}", + item.GetType().Name, + item.Name ?? "Unknown name", + fileSystemInfo.FullName, + item.Id); + } catch (IOException) { if (isRequiredForDelete) -- cgit v1.2.3 From 7d28d08e08a412ab88ede368220562799f2bd7c0 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 13 Apr 2024 01:45:01 +0200 Subject: Enable more warnings as errors (#11288) --- Emby.Server.Implementations/ApplicationHost.cs | 14 +++++++------- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 5 +---- Emby.Server.Implementations/Dto/DtoService.cs | 7 ++++--- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- Emby.Server.Implementations/Library/ResolverHelper.cs | 2 +- .../Library/Resolvers/Movies/BoxSetResolver.cs | 2 +- Emby.Server.Implementations/Session/SessionManager.cs | 5 +---- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 10 +++++----- jellyfin.ruleset | 6 ++++++ 9 files changed, 27 insertions(+), 26 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index acabbb059b..6add7e0b39 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -109,13 +109,13 @@ namespace Emby.Server.Implementations /// /// The disposable parts. /// - private readonly ConcurrentDictionary _disposableParts = new(); + private readonly ConcurrentBag _disposableParts = new(); private readonly DeviceId _deviceId; private readonly IConfiguration _startupConfig; private readonly IXmlSerializer _xmlSerializer; private readonly IStartupOptions _startupOptions; - private readonly IPluginManager _pluginManager; + private readonly PluginManager _pluginManager; private List _creatingInstances; @@ -161,7 +161,7 @@ namespace Emby.Server.Implementations ApplicationPaths.PluginsPath, ApplicationVersion); - _disposableParts.TryAdd((PluginManager)_pluginManager, byte.MinValue); + _disposableParts.Add(_pluginManager); } /// @@ -360,7 +360,7 @@ namespace Emby.Server.Implementations { foreach (var part in parts.OfType()) { - _disposableParts.TryAdd(part, byte.MinValue); + _disposableParts.Add(part); } } @@ -381,7 +381,7 @@ namespace Emby.Server.Implementations { foreach (var part in parts.OfType()) { - _disposableParts.TryAdd(part, byte.MinValue); + _disposableParts.Add(part); } } @@ -457,7 +457,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ConfigurationManager); serviceCollection.AddSingleton(ConfigurationManager); serviceCollection.AddSingleton(this); - serviceCollection.AddSingleton(_pluginManager); + serviceCollection.AddSingleton(_pluginManager); serviceCollection.AddSingleton(ApplicationPaths); serviceCollection.AddSingleton(); @@ -965,7 +965,7 @@ namespace Emby.Server.Implementations Logger.LogInformation("Disposing {Type}", type.Name); - foreach (var (part, _) in _disposableParts) + foreach (var part in _disposableParts.ToArray()) { var partType = part.GetType(); if (partType == type) diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index bf079d90ca..b1c99227c3 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -186,10 +186,7 @@ namespace Emby.Server.Implementations.Data protected void CheckDisposed() { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name, "Object has been disposed and cannot be accessed."); - } + ObjectDisposedException.ThrowIf(_disposed, this); } /// diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 5da9bea262..98eacb52b2 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -668,12 +668,13 @@ namespace Emby.Server.Implementations.Dto { dto.ImageBlurHashes ??= new Dictionary>(); - if (!dto.ImageBlurHashes.ContainsKey(image.Type)) + if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value)) { - dto.ImageBlurHashes[image.Type] = new Dictionary(); + value = new Dictionary(); + dto.ImageBlurHashes[image.Type] = value; } - dto.ImageBlurHashes[image.Type][tag] = image.BlurHash; + value[tag] = image.BlurHash; } return tag; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index dced6868d7..bb5cc746e9 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -461,7 +461,7 @@ namespace Emby.Server.Implementations.Library ReportItemRemoved(item, parent); } - private static IEnumerable GetMetadataPaths(BaseItem item, IEnumerable children) + private static List GetMetadataPaths(BaseItem item, IEnumerable children) { var list = new List { diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 7a61e2607c..52be76217e 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.Library item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); - item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || + item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) || item.GetParents().Any(i => i.IsLocked); // Make sure DateCreated and DateModified have values diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index 6cc04ea810..955055313e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } - if (filename.IndexOf("[boxset]", StringComparison.OrdinalIgnoreCase) != -1 || args.ContainsFileSystemEntryByName("collection.xml")) + if (filename.Contains("[boxset]", StringComparison.OrdinalIgnoreCase) || args.ContainsFileSystemEntryByName("collection.xml")) { return new BoxSet { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 75945b08a2..06798628f3 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -159,10 +159,7 @@ namespace Emby.Server.Implementations.Session private void CheckDisposed() { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } + ObjectDisposedException.ThrowIf(_disposed, this); } private void OnSessionStarted(SessionInfo info) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 717b53a0b6..eb375c8a25 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1271,23 +1271,23 @@ namespace MediaBrowser.Controller.MediaEncoding { var codec = stream.Codec ?? string.Empty; - return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 - || codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; + return codec.Contains("264", StringComparison.OrdinalIgnoreCase) + || codec.Contains("avc", StringComparison.OrdinalIgnoreCase); } public static bool IsH265(MediaStream stream) { var codec = stream.Codec ?? string.Empty; - return codec.IndexOf("265", StringComparison.OrdinalIgnoreCase) != -1 - || codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1; + return codec.Contains("265", StringComparison.OrdinalIgnoreCase) + || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase); } public static bool IsAAC(MediaStream stream) { var codec = stream.Codec ?? string.Empty; - return codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1; + return codec.Contains("aac", StringComparison.OrdinalIgnoreCase); } public static string GetBitStreamArgs(MediaStream stream) diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 10225e3af8..db116f46c8 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -85,6 +85,8 @@ + + @@ -101,6 +103,8 @@ + + @@ -108,6 +112,8 @@ + + -- cgit v1.2.3 From 204146a3a504c4cc417c9eb68c32271e3f585352 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 13 Apr 2024 14:48:40 +0800 Subject: fix: mark UserRoot as non-root when performing removal Fixes #11269 Signed-off-by: gnattu --- Emby.Server.Implementations/Library/LibraryManager.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index bb5cc746e9..baed887e3e 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1033,7 +1033,7 @@ namespace Emby.Server.Implementations.Library } } - private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken) + private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken, bool removeRoot = false) { await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); @@ -1046,11 +1046,15 @@ namespace Emby.Server.Implementations.Library await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false); + // HACK: override IsRootHere for libraries to be removed + if (removeRoot) GetUserRootFolder().IsRoot = false; await GetUserRootFolder().ValidateChildren( new Progress(), new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: false, cancellationToken).ConfigureAwait(false); + // HACK: restore IsRoot here after validation + if (removeRoot) GetUserRootFolder().IsRoot = true; // Quickly scan CollectionFolders for changes foreach (var folder in GetUserRootFolder().Children.OfType()) @@ -3118,7 +3122,7 @@ namespace Emby.Server.Implementations.Library if (refreshLibrary) { - await ValidateTopLibraryFolders(CancellationToken.None).ConfigureAwait(false); + await ValidateTopLibraryFolders(CancellationToken.None, true).ConfigureAwait(false); StartScanInBackground(); } -- cgit v1.2.3 From 4fa6b8874f0cc773a977b09aec249d207a1335d3 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 13 Apr 2024 14:58:29 +0800 Subject: fix: typo Signed-off-by: gnattu --- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index baed887e3e..da6c756743 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1046,7 +1046,7 @@ namespace Emby.Server.Implementations.Library await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false); - // HACK: override IsRootHere for libraries to be removed + // HACK: override IsRoot here for libraries to be removed if (removeRoot) GetUserRootFolder().IsRoot = false; await GetUserRootFolder().ValidateChildren( new Progress(), -- cgit v1.2.3 From 7befbda1a66ed0a0c0f386081e13c0b2585b8137 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 13 Apr 2024 15:02:13 +0800 Subject: fix: code style Signed-off-by: gnattu --- Emby.Server.Implementations/Library/LibraryManager.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index da6c756743..8f5f366883 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1047,14 +1047,21 @@ namespace Emby.Server.Implementations.Library await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false); // HACK: override IsRoot here for libraries to be removed - if (removeRoot) GetUserRootFolder().IsRoot = false; + if (removeRoot) + { + GetUserRootFolder().IsRoot = false; + } + await GetUserRootFolder().ValidateChildren( new Progress(), new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: false, cancellationToken).ConfigureAwait(false); // HACK: restore IsRoot here after validation - if (removeRoot) GetUserRootFolder().IsRoot = true; + if (removeRoot) + { + GetUserRootFolder().IsRoot = true; + } // Quickly scan CollectionFolders for changes foreach (var folder in GetUserRootFolder().Children.OfType()) -- cgit v1.2.3 From 8026202d7e3ea08fd81a1404b08d07b9b83f4dcc Mon Sep 17 00:00:00 2001 From: Moe Ye Htet Date: Sun, 14 Apr 2024 02:56:46 +0000 Subject: Translated using Weblate (Burmese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/my/ --- Emby.Server.Implementations/Localization/Core/my.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Localization/Core/my.json b/Emby.Server.Implementations/Localization/Core/my.json index 198f7540c8..4cb4cdc757 100644 --- a/Emby.Server.Implementations/Localization/Core/my.json +++ b/Emby.Server.Implementations/Localization/Core/my.json @@ -48,7 +48,7 @@ "Undefined": "သတ်မှတ်မထားသော", "TvShows": "တီဗီ ဇာတ်လမ်းတွဲများ", "System": "စနစ်", - "Sync": "ထပ်တူကျသည်။", + "Sync": "ချိန်ကိုက်မည်", "SubtitleDownloadFailureFromForItem": "{1} အတွက် {0} မှ စာတန်းထိုးများ ဒေါင်းလုဒ်လုပ်ခြင်း မအောင်မြင်ပါ", "StartupEmbyServerIsLoading": "Jellyfin ဆာဗာကို အသင့်ပြင်နေပါသည်။ ခဏနေ ထပ်စမ်းကြည့်ပါ။", "Songs": "သီချင်းများ", @@ -104,7 +104,7 @@ "HeaderFavoriteSongs": "အကြိုက်ဆုံးသီချင်းများ", "HeaderFavoriteShows": "အကြိုက်ဆုံး ဇာတ်လမ်းတွဲများ", "HeaderFavoriteEpisodes": "အကြိုက်ဆုံး ဇာတ်လမ်းအပိုင်းများ", - "HeaderFavoriteArtists": "အကြိုက်ဆုံးအနုပညာရှင်များ", + "HeaderFavoriteArtists": "အကြိုက်ဆုံး အနုပညာရှင်များ", "HeaderFavoriteAlbums": "အကြိုက်ဆုံး အယ်လ်ဘမ်များ", "HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ", "HeaderAlbumArtists": "အယ်လ်ဘမ်အနုပညာရှင်များ", @@ -120,5 +120,11 @@ "AuthenticationSucceededWithUserName": "{0} အောင်မြင်စွာ စစ်မှန်ကြောင်း အတည်ပြုပြီးပါပြီ", "Application": "အပလီကေးရှင်း", "AppDeviceValues": "အက်ပ်- {0}၊ စက်- {1}", - "External": "ပြင်ပ" + "External": "ပြင်ပ", + "TaskKeyframeExtractorDescription": "ပိုမိုတိကျသည့် အိတ်ချ်အယ်လ်အက်စ် အစဉ်လိုက်ပြသမှုများ ဖန်တီးနိုင်ရန်အတွက် ဗီဒီယိုဖိုင်များမှ ကီးဖရိန်များကို ထုတ်နှုတ်ယူမည် ဖြစ်သည်။ ဤလုပ်ဆောင်မှုသည် အချိန်ကြာရှည်နိုင်သည်။", + "TaskCleanCollectionsAndPlaylistsDescription": "စုစည်းမှုများနှင့် အစဉ်လိုက်ပြသမှုများမှ မရှိတော့သည်များကို ဖယ်ရှားမည်။", + "TaskRefreshTrickplayImages": "ထရစ်ခ်ပလေး ပုံများကို ထုတ်မည်", + "TaskKeyframeExtractor": "ကီးဖရိန်များကို ထုတ်နုတ်ခြင်း", + "TaskCleanCollectionsAndPlaylists": "စုစည်းမှုများနှင့် အစဉ်လိုက်ပြသမှုများကို ရှင်းလင်းမည်", + "HearingImpaired": "အကြားအာရုံ ချို့တဲ့သူ" } -- cgit v1.2.3 From 6fb6b5f1766a1f37a61b9faaa40209bab995bf30 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 14 Apr 2024 08:18:36 -0600 Subject: Validate item access (#11171) --- .../Library/LibraryManager.cs | 39 +++++-- .../Controllers/DisplayPreferencesController.cs | 2 +- Jellyfin.Api/Controllers/FilterController.cs | 2 +- Jellyfin.Api/Controllers/ImageController.cs | 20 ++-- Jellyfin.Api/Controllers/InstantMixController.cs | 60 +++++++++-- Jellyfin.Api/Controllers/ItemLookupController.cs | 13 ++- Jellyfin.Api/Controllers/ItemRefreshController.cs | 5 +- Jellyfin.Api/Controllers/ItemUpdateController.cs | 12 ++- Jellyfin.Api/Controllers/ItemsController.cs | 12 ++- Jellyfin.Api/Controllers/LibraryController.cs | 65 ++++++------ .../Controllers/LibraryStructureController.cs | 12 ++- Jellyfin.Api/Controllers/LiveTvController.cs | 27 ++++- Jellyfin.Api/Controllers/LyricsController.cs | 58 ++++------- Jellyfin.Api/Controllers/MediaInfoController.cs | 42 ++++++-- Jellyfin.Api/Controllers/PlaylistsController.cs | 7 +- Jellyfin.Api/Controllers/PlaystateController.cs | 21 ++-- Jellyfin.Api/Controllers/RemoteImageController.cs | 9 +- Jellyfin.Api/Controllers/SearchController.cs | 2 +- Jellyfin.Api/Controllers/SubtitleController.cs | 50 ++++++--- Jellyfin.Api/Controllers/TrickplayController.cs | 4 +- Jellyfin.Api/Controllers/TvShowsController.cs | 15 +-- .../Controllers/UniversalAudioController.cs | 27 +++-- Jellyfin.Api/Controllers/UserLibraryController.cs | 116 +++++---------------- .../Controllers/VideoAttachmentsController.cs | 5 +- Jellyfin.Api/Controllers/VideosController.cs | 31 +++--- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 14 +-- Jellyfin.Api/Helpers/StreamingHelpers.cs | 5 +- MediaBrowser.Controller/Library/ILibraryManager.cs | 20 ++++ 28 files changed, 414 insertions(+), 281 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index bb5cc746e9..0a4432beca 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -46,6 +46,7 @@ using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using TMDbLib.Objects.Authentication; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using EpisodeInfo = Emby.Naming.TV.EpisodeInfo; using Genre = MediaBrowser.Controller.Entities.Genre; @@ -1222,12 +1223,7 @@ namespace Emby.Server.Implementations.Library return null; } - /// - /// Gets the item by id. - /// - /// The id. - /// BaseItem. - /// is null. + /// public BaseItem GetItemById(Guid id) { if (id.IsEmpty()) @@ -1263,6 +1259,22 @@ namespace Emby.Server.Implementations.Library return null; } + /// + public T GetItemById(Guid id, Guid userId) + where T : BaseItem + { + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); + return GetItemById(id, user); + } + + /// + public T GetItemById(Guid id, User user) + where T : BaseItem + { + var item = GetItemById(id); + return ItemIsVisible(item, user) ? item : null; + } + public List GetItemList(InternalItemsQuery query, bool allowExternalContent) { if (query.Recursive && !query.ParentId.IsEmpty()) @@ -3191,5 +3203,20 @@ namespace Emby.Server.Implementations.Library CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); } + + private static bool ItemIsVisible(BaseItem item, User user) + { + if (item is null) + { + return false; + } + + if (user is null) + { + return true; + } + + return item is UserRootFolder || item.IsVisibleStandalone(user); + } } } diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 1cad663264..6d94d96f3a 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -194,7 +194,7 @@ public class DisplayPreferencesController : BaseJellyfinApiController foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.OrdinalIgnoreCase))) { - if (!Enum.TryParse(displayPreferences.CustomPrefs[key], true, out var type)) + if (!Enum.TryParse(displayPreferences.CustomPrefs[key], true, out _)) { _logger.LogError("Invalid ViewType: {LandingScreenOption}", displayPreferences.CustomPrefs[key]); displayPreferences.CustomPrefs.Remove(key); diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index d6e043e6a1..4abca32713 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -162,7 +162,7 @@ public class FilterController : BaseJellyfinApiController } else if (parentId.HasValue) { - parentItem = _libraryManager.GetItemById(parentId.Value); + parentItem = _libraryManager.GetItemById(parentId.Value); } var filters = new QueryFilters(); diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 6b38fa7d34..8e8accab3c 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -90,6 +90,7 @@ public class ImageController : BaseJellyfinApiController /// User Id. /// Image updated. /// User does not have permission to delete the image. + /// Item not found. /// A . [HttpPost("UserImage")] [Authorize] @@ -97,6 +98,7 @@ public class ImageController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task PostUserImage( [FromQuery] Guid? userId) { @@ -289,7 +291,7 @@ public class ImageController : BaseJellyfinApiController [FromRoute, Required] ImageType imageType, [FromQuery] int? imageIndex) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -317,7 +319,7 @@ public class ImageController : BaseJellyfinApiController [FromRoute, Required] ImageType imageType, [FromRoute] int imageIndex) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -346,7 +348,7 @@ public class ImageController : BaseJellyfinApiController [FromRoute, Required] Guid itemId, [FromRoute, Required] ImageType imageType) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -390,7 +392,7 @@ public class ImageController : BaseJellyfinApiController [FromRoute, Required] ImageType imageType, [FromRoute] int imageIndex) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -433,7 +435,7 @@ public class ImageController : BaseJellyfinApiController [FromRoute, Required] int imageIndex, [FromQuery, Required] int newIndex) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -456,7 +458,7 @@ public class ImageController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task>> GetItemImageInfos([FromRoute, Required] Guid itemId) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -559,7 +561,7 @@ public class ImageController : BaseJellyfinApiController [FromQuery] string? foregroundLayer, [FromQuery] int? imageIndex) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -637,7 +639,7 @@ public class ImageController : BaseJellyfinApiController [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -715,7 +717,7 @@ public class ImageController : BaseJellyfinApiController [FromQuery] string? foregroundLayer, [FromRoute, Required] int imageIndex) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 3cf4852995..dcbacf1d78 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -62,9 +62,11 @@ public class InstantMixController : BaseJellyfinApiController /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Instant playlist returned. + /// Item not found. /// A with the playlist items. [HttpGet("Songs/{itemId}/InstantMix")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetInstantMixFromSong( [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, @@ -75,11 +77,16 @@ public class InstantMixController : BaseJellyfinApiController [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { - var item = _libraryManager.GetItemById(itemId); userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById(itemId, user); + if (item is null) + { + return NotFound(); + } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -99,9 +106,11 @@ public class InstantMixController : BaseJellyfinApiController /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Instant playlist returned. + /// Item not found. /// A with the playlist items. [HttpGet("Albums/{itemId}/InstantMix")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetInstantMixFromAlbum( [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, @@ -112,15 +121,20 @@ public class InstantMixController : BaseJellyfinApiController [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { - var album = _libraryManager.GetItemById(itemId); userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById(itemId, user); + if (item is null) + { + return NotFound(); + } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions); + var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); return GetResult(items, user, limit, dtoOptions); } @@ -136,9 +150,11 @@ public class InstantMixController : BaseJellyfinApiController /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Instant playlist returned. + /// Item not found. /// A with the playlist items. [HttpGet("Playlists/{itemId}/InstantMix")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetInstantMixFromPlaylist( [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, @@ -149,15 +165,20 @@ public class InstantMixController : BaseJellyfinApiController [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { - var playlist = (Playlist)_libraryManager.GetItemById(itemId); userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById(itemId, user); + if (item is null) + { + return NotFound(); + } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); - var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions); + var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); return GetResult(items, user, limit, dtoOptions); } @@ -209,9 +230,11 @@ public class InstantMixController : BaseJellyfinApiController /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Instant playlist returned. + /// Item not found. /// A with the playlist items. [HttpGet("Artists/{itemId}/InstantMix")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetInstantMixFromArtists( [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, @@ -222,11 +245,16 @@ public class InstantMixController : BaseJellyfinApiController [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { - var item = _libraryManager.GetItemById(itemId); userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById(itemId, user); + if (item is null) + { + return NotFound(); + } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -246,9 +274,11 @@ public class InstantMixController : BaseJellyfinApiController /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Instant playlist returned. + /// Item not found. /// A with the playlist items. [HttpGet("Items/{itemId}/InstantMix")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetInstantMixFromItem( [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, @@ -259,11 +289,16 @@ public class InstantMixController : BaseJellyfinApiController [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { - var item = _libraryManager.GetItemById(itemId); userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById(itemId, user); + if (item is null) + { + return NotFound(); + } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); @@ -283,9 +318,11 @@ public class InstantMixController : BaseJellyfinApiController /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Instant playlist returned. + /// Item not found. /// A with the playlist items. [HttpGet("Artists/InstantMix")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Obsolete("Use GetInstantMixFromArtists")] public ActionResult> GetInstantMixFromArtists2( [FromQuery, Required] Guid id, @@ -320,9 +357,11 @@ public class InstantMixController : BaseJellyfinApiController /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Instant playlist returned. + /// Item not found. /// A with the playlist items. [HttpGet("MusicGenres/InstantMix")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetInstantMixFromMusicGenreById( [FromQuery, Required] Guid id, [FromQuery] Guid? userId, @@ -333,11 +372,16 @@ public class InstantMixController : BaseJellyfinApiController [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { - var item = _libraryManager.GetItemById(id); userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById(id, user); + if (item is null) + { + return NotFound(); + } + var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index e3aee1bf7a..d009f80a96 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -4,6 +4,8 @@ using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -64,7 +66,7 @@ public class ItemLookupController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetExternalIdInfos([FromRoute, Required] Guid itemId) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -234,6 +236,7 @@ public class ItemLookupController : BaseJellyfinApiController /// The remote search result. /// Optional. Whether or not to replace all images. Default: True. /// Item metadata refreshed. + /// Item not found. /// /// A that represents the asynchronous operation to get the remote search results. /// The task result contains an . @@ -241,12 +244,18 @@ public class ItemLookupController : BaseJellyfinApiController [HttpPost("Items/RemoteSearch/Apply/{itemId}")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task ApplySearchCriteria( [FromRoute, Required] Guid itemId, [FromBody, Required] RemoteSearchResult searchResult, [FromQuery] bool replaceAllImages = true) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } + _logger.LogInformation( "Setting provider id's to item {ItemId}-{ItemName}: {@ProviderIds}", item.Id, diff --git a/Jellyfin.Api/Controllers/ItemRefreshController.cs b/Jellyfin.Api/Controllers/ItemRefreshController.cs index 0a8522e1cf..c1343b1309 100644 --- a/Jellyfin.Api/Controllers/ItemRefreshController.cs +++ b/Jellyfin.Api/Controllers/ItemRefreshController.cs @@ -2,7 +2,10 @@ using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; using MediaBrowser.Common.Api; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; @@ -61,7 +64,7 @@ public class ItemRefreshController : BaseJellyfinApiController [FromQuery] bool replaceAllMetadata = false, [FromQuery] bool replaceAllImages = false) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 9800248c68..83f308bb19 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Constants; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; using Jellyfin.Data.Enums; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Configuration; @@ -72,7 +74,7 @@ public class ItemUpdateController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task UpdateItem([FromRoute, Required] Guid itemId, [FromBody, Required] BaseItemDto request) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -145,7 +147,11 @@ public class ItemUpdateController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetMetadataEditorInfo([FromRoute, Required] Guid itemId) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); + if (item is null) + { + return NotFound(); + } var info = new MetadataEditorInfo { @@ -197,7 +203,7 @@ public class ItemUpdateController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string? contentType) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 26ae1a820f..6ffe6e7da1 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -967,9 +967,13 @@ public class ItemsController : BaseJellyfinApiController } var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException(); - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, user); + if (item is null) + { + return NotFound(); + } - return (item == null) ? NotFound() : _userDataRepository.GetUserDataDto(item, user); + return _userDataRepository.GetUserDataDto(item, user); } /// @@ -1014,8 +1018,8 @@ public class ItemsController : BaseJellyfinApiController } var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException(); - var item = _libraryManager.GetItemById(itemId); - if (item == null) + var item = _libraryManager.GetItemById(itemId, user); + if (item is null) { return NotFound(); } diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 360389d292..3b4e80ff3c 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -102,7 +102,7 @@ public class LibraryController : BaseJellyfinApiController [ProducesFile("video/*", "audio/*")] public ActionResult GetFile([FromRoute, Required] Guid itemId) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, User.GetUserId()); if (item is null) { return NotFound(); @@ -152,11 +152,10 @@ public class LibraryController : BaseJellyfinApiController ? (userId.IsNullOrEmpty() ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) - : _libraryManager.GetItemById(itemId); - + : _libraryManager.GetItemById(itemId, user); if (item is null) { - return NotFound("Item not found."); + return NotFound(); } IEnumerable themeItems; @@ -214,16 +213,14 @@ public class LibraryController : BaseJellyfinApiController var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = itemId.IsEmpty() ? (userId.IsNullOrEmpty() ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) - : _libraryManager.GetItemById(itemId); - + : _libraryManager.GetItemById(itemId, user); if (item is null) { - return NotFound("Item not found."); + return NotFound(); } IEnumerable themeItems; @@ -286,7 +283,8 @@ public class LibraryController : BaseJellyfinApiController userId, inheritFromParent); - if (themeSongs.Result is NotFoundObjectResult || themeVideos.Result is NotFoundObjectResult) + if (themeSongs.Result is StatusCodeResult { StatusCode: StatusCodes.Status404NotFound } + || themeVideos.Result is StatusCodeResult { StatusCode: StatusCodes.Status404NotFound }) { return NotFound(); } @@ -327,6 +325,7 @@ public class LibraryController : BaseJellyfinApiController /// The item id. /// Item deleted. /// Unauthorized access. + /// Item not found. /// A . [HttpDelete("Items/{itemId}")] [Authorize] @@ -335,17 +334,18 @@ public class LibraryController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult DeleteItem(Guid itemId) { - var isApiKey = User.GetIsApiKey(); var userId = User.GetUserId(); - var user = !isApiKey && !userId.IsEmpty() - ? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException() - : null; - if (!isApiKey && user is null) + var isApiKey = User.GetIsApiKey(); + var user = userId.IsEmpty() && isApiKey + ? null + : _userManager.GetUserById(userId); + + if (user is null && !isApiKey) { - return Unauthorized("Unauthorized access"); + return NotFound(); } - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId, user); if (item is null) { return NotFound(); @@ -391,7 +391,7 @@ public class LibraryController : BaseJellyfinApiController foreach (var i in ids) { - var item = _libraryManager.GetItemById(i); + var item = _libraryManager.GetItemById(i, user); if (item is null) { return NotFound(); @@ -459,20 +459,18 @@ public class LibraryController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId) { - var item = _libraryManager.GetItemById(itemId); userId = RequestHelpers.GetUserId(User, userId); - + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); + var item = _libraryManager.GetItemById(itemId, user); if (item is null) { - return NotFound("Item not found"); + return NotFound(); } var baseItemDtos = new List(); - var user = userId.IsNullOrEmpty() - ? null - : _userManager.GetUserById(userId.Value); - var dtoOptions = new DtoOptions().AddClientFields(User); BaseItem? parent = item.GetParent(); @@ -644,14 +642,16 @@ public class LibraryController : BaseJellyfinApiController [ProducesFile("video/*", "audio/*")] public async Task GetDownload([FromRoute, Required] Guid itemId) { - var item = _libraryManager.GetItemById(itemId); + var userId = User.GetUserId(); + var user = userId.IsEmpty() + ? null + : _userManager.GetUserById(userId); + var item = _libraryManager.GetItemById(itemId, user); if (item is null) { return NotFound(); } - var user = _userManager.GetUserById(User.GetUserId()); - if (user is not null) { if (!item.CanDownload(user)) @@ -704,12 +704,14 @@ public class LibraryController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) { userId = RequestHelpers.GetUserId(User, userId); + var user = userId.IsNullOrEmpty() + ? null + : _userManager.GetUserById(userId.Value); var item = itemId.IsEmpty() - ? (userId.IsNullOrEmpty() + ? (user is null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) - : _libraryManager.GetItemById(itemId); - + : _libraryManager.GetItemById(itemId, user); if (item is null) { return NotFound(); @@ -720,9 +722,6 @@ public class LibraryController : BaseJellyfinApiController return new QueryResult(); } - var user = userId.IsNullOrEmpty() - ? null - : _userManager.GetUserById(userId.Value); var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User); diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index 23c430f859..c1d01a5c2f 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -6,6 +6,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LibraryStructureDto; using MediaBrowser.Common.Api; @@ -311,15 +313,21 @@ public class LibraryStructureController : BaseJellyfinApiController /// /// The library name and options. /// Library updated. + /// Item not found. /// A . [HttpPost("LibraryOptions")] [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateLibraryOptions( [FromBody] UpdateLibraryOptionsDto request) { - var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(request.Id); + var item = _libraryManager.GetItemById(request.Id, User.GetUserId()); + if (item is null) + { + return NotFound(); + } - collectionFolder.UpdateLibraryOptions(request.LibraryOptions); + item.UpdateLibraryOptions(request.LibraryOptions); return NoContent(); } } diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 7768b3c45f..2b26c01f88 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -220,9 +220,11 @@ public class LiveTvController : BaseJellyfinApiController /// Channel id. /// Optional. Attach user data. /// Live tv channel returned. + /// Item not found. /// An containing the live tv channel. [HttpGet("Channels/{channelId}")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Authorize(Policy = Policies.LiveTvAccess)] public ActionResult GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId) { @@ -232,7 +234,12 @@ public class LiveTvController : BaseJellyfinApiController : _userManager.GetUserById(userId.Value); var item = channelId.IsEmpty() ? _libraryManager.GetUserRootFolder() - : _libraryManager.GetItemById(channelId); + : _libraryManager.GetItemById(channelId, user); + + if (item is null) + { + return NotFound(); + } var dtoOptions = new DtoOptions() .AddClientFields(User); @@ -416,9 +423,11 @@ public class LiveTvController : BaseJellyfinApiController /// Recording id. /// Optional. Attach user data. /// Recording returned. + /// Item not found. /// An containing the live tv recording. [HttpGet("Recordings/{recordingId}")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [Authorize(Policy = Policies.LiveTvAccess)] public ActionResult GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId) { @@ -426,7 +435,13 @@ public class LiveTvController : BaseJellyfinApiController var user = userId.IsNullOrEmpty() ? null : _userManager.GetUserById(userId.Value); - var item = recordingId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId); + var item = recordingId.IsEmpty() + ? _libraryManager.GetUserRootFolder() + : _libraryManager.GetItemById(recordingId, user); + if (item is null) + { + return NotFound(); + } var dtoOptions = new DtoOptions() .AddClientFields(User); @@ -611,7 +626,8 @@ public class LiveTvController : BaseJellyfinApiController { query.IsSeries = true; - if (_libraryManager.GetItemById(librarySeriesId.Value) is Series series) + var series = _libraryManager.GetItemById(librarySeriesId.Value); + if (series is not null) { query.Name = series.Name; } @@ -665,7 +681,8 @@ public class LiveTvController : BaseJellyfinApiController { query.IsSeries = true; - if (_libraryManager.GetItemById(body.LibrarySeriesId) is Series series) + var series = _libraryManager.GetItemById(body.LibrarySeriesId); + if (series is not null) { query.Name = series.Name; } @@ -779,7 +796,7 @@ public class LiveTvController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId) { - var item = _libraryManager.GetItemById(recordingId); + var item = _libraryManager.GetItemById(recordingId, User.GetUserId()); if (item is null) { return NotFound(); diff --git a/Jellyfin.Api/Controllers/LyricsController.cs b/Jellyfin.Api/Controllers/LyricsController.cs index f2b312b478..8eb4cadf88 100644 --- a/Jellyfin.Api/Controllers/LyricsController.cs +++ b/Jellyfin.Api/Controllers/LyricsController.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Entities.Audio; @@ -66,37 +67,16 @@ public class LyricsController : BaseJellyfinApiController [HttpGet("Audio/{itemId}/Lyrics")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetLyrics([FromRoute, Required] Guid itemId) { - var isApiKey = User.GetIsApiKey(); - var userId = User.GetUserId(); - if (!isApiKey && userId.IsEmpty()) - { - return BadRequest(); - } - - var audio = _libraryManager.GetItemById