Browse Source

Implement update endpoint

Shadowghost 1 year ago
parent
commit
c1dbb49315

+ 58 - 30
Emby.Server.Implementations/Playlists/PlaylistManager.cs

@@ -71,56 +71,56 @@ namespace Emby.Server.Implementations.Playlists
             return GetPlaylistsFolder(userId).GetChildren(user, true).OfType<Playlist>();
         }
 
-        public async Task<PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest options)
+        public async Task<PlaylistCreationResult> 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);
         }
 
         /// <inheritdoc />
@@ -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);
 

+ 42 - 32
Jellyfin.Api/Controllers/PlaylistsController.cs

@@ -101,72 +101,82 @@ public class PlaylistsController : BaseJellyfinApiController
     }
 
     /// <summary>
-    /// Get a playlist's users.
+    /// Updates a playlist.
     /// </summary>
     /// <param name="playlistId">The playlist id.</param>
-    /// <response code="200">Found shares.</response>
+    /// <param name="updatePlaylistRequest">The <see cref="UpdatePlaylistDto"/> id.</param>
+    /// <response code="204">Playlist updated.</response>
     /// <response code="401">Unauthorized access.</response>
     /// <response code="404">Playlist not found.</response>
     /// <returns>
-    /// A list of <see cref="PlaylistUserPermissions"/> objects.
+    /// A <see cref="Task" /> that represents the asynchronous operation to update a playlist.
+    /// The task result contains an <see cref="OkResult"/> indicating success.
     /// </returns>
-    [HttpGet("{playlistId}/User")]
+    [HttpPost("{playlistId}")]
     [ProducesResponseType(StatusCodes.Status200OK)]
     [ProducesResponseType(StatusCodes.Status401Unauthorized)]
-    [ProducesResponseType(StatusCodes.Status404NotFound)]
-    public ActionResult<IReadOnlyList<PlaylistUserPermissions>> GetPlaylistUsers(
-        [FromRoute, Required] Guid playlistId)
+    public async Task<ActionResult> 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();
     }
 
     /// <summary>
-    /// Toggles public access of a playlist.
+    /// Get a playlist's users.
     /// </summary>
     /// <param name="playlistId">The playlist id.</param>
-    /// <response code="204">Public access toggled.</response>
+    /// <response code="200">Found shares.</response>
     /// <response code="401">Unauthorized access.</response>
     /// <response code="404">Playlist not found.</response>
     /// <returns>
-    /// A <see cref="Task" /> that represents the asynchronous operation to toggle public access of a playlist.
-    /// The task result contains an <see cref="OkResult"/> indicating success.
+    /// A list of <see cref="PlaylistUserPermissions"/> objects.
     /// </returns>
-    [HttpPost("{playlistId}/TogglePublic")]
-    [ProducesResponseType(StatusCodes.Status204NoContent)]
+    [HttpGet("{playlistId}/User")]
+    [ProducesResponseType(StatusCodes.Status200OK)]
     [ProducesResponseType(StatusCodes.Status401Unauthorized)]
-    public async Task<ActionResult> TogglePublicAccess(
+    [ProducesResponseType(StatusCodes.Status404NotFound)]
+    public ActionResult<IReadOnlyList<PlaylistUserPermissions>> 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");
     }
 
     /// <summary>
@@ -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();
     }

+ 34 - 0
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;
+
+/// <summary>
+/// Updateexisting playlist dto.
+/// </summary>
+public class UpdatePlaylistDto
+{
+    /// <summary>
+    /// Gets or sets the name of the new playlist.
+    /// </summary>
+    public string? Name { get; set; }
+
+    /// <summary>
+    /// Gets or sets item ids of the playlist.
+    /// </summary>
+    [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
+    public IReadOnlyList<Guid>? Ids { get; set; }
+
+    /// <summary>
+    /// Gets or sets the playlist users.
+    /// </summary>
+    public IReadOnlyList<PlaylistUserPermissions>? Users { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the playlist is public.
+    /// </summary>
+    public bool? Public { get; set; }
+}

+ 2 - 2
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);

+ 14 - 15
MediaBrowser.Controller/Playlists/IPlaylistManager.cs

@@ -20,19 +20,25 @@ namespace MediaBrowser.Controller.Playlists
         Playlist GetPlaylist(Guid userId, Guid playlistId);
 
         /// <summary>
-        /// Gets the playlists.
+        /// Creates the playlist.
         /// </summary>
-        /// <param name="userId">The user identifier.</param>
-        /// <returns>IEnumerable&lt;Playlist&gt;.</returns>
-        IEnumerable<Playlist> GetPlaylists(Guid userId);
+        /// <param name="request">The <see cref="PlaylistCreationRequest"/>.</param>
+        /// <returns>Task&lt;Playlist&gt;.</returns>
+        Task<PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest request);
 
         /// <summary>
-        /// Toggle OpenAccess policy of the playlist.
+        /// Updates a playlist.
         /// </summary>
-        /// <param name="playlistId">The playlist identifier.</param>
-        /// <param name="userId">The user identifier.</param>
+        /// <param name="request">The <see cref="PlaylistUpdateRequest"/>.</param>
         /// <returns>Task.</returns>
-        Task ToggleOpenAccess(Guid playlistId, Guid userId);
+        Task UpdatePlaylist(PlaylistUpdateRequest request);
+
+        /// <summary>
+        /// Gets the playlists.
+        /// </summary>
+        /// <param name="userId">The user identifier.</param>
+        /// <returns>IEnumerable&lt;Playlist&gt;.</returns>
+        IEnumerable<Playlist> GetPlaylists(Guid userId);
 
         /// <summary>
         /// Adds a share to the playlist.
@@ -52,13 +58,6 @@ namespace MediaBrowser.Controller.Playlists
         /// <returns>Task.</returns>
         Task RemoveFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share);
 
-        /// <summary>
-        /// Creates the playlist.
-        /// </summary>
-        /// <param name="options">The options.</param>
-        /// <returns>Task&lt;Playlist&gt;.</returns>
-        Task<PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest options);
-
         /// <summary>
         /// Adds to playlist.
         /// </summary>

+ 1 - 1
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)

+ 2 - 2
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;

+ 8 - 11
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);

+ 41 - 0
MediaBrowser.Model/Playlists/PlaylistUpdateRequest.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Model.Playlists;
+
+/// <summary>
+/// A playlist creation request.
+/// </summary>
+public class PlaylistUpdateRequest
+{
+    /// <summary>
+    /// Gets or sets the id of the playlist.
+    /// </summary>
+    public Guid Id { get; set; }
+
+    /// <summary>
+    /// Gets or sets the id of the user updating the playlist.
+    /// </summary>
+    public Guid UserId { get; set; }
+
+    /// <summary>
+    /// Gets or sets the name of the playlist.
+    /// </summary>
+    public string? Name { get; set; }
+
+    /// <summary>
+    /// Gets or sets item ids to add to the playlist.
+    /// </summary>
+    public IReadOnlyList<Guid>? Ids { get; set; }
+
+    /// <summary>
+    /// Gets or sets the playlist users.
+    /// </summary>
+    public IReadOnlyList<PlaylistUserPermissions>? Users { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the playlist is public.
+    /// </summary>
+    public bool? Public { get; set; }
+}