< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.Playlists.PlaylistManager
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/Playlists/PlaylistManager.cs
Line coverage
3%
Covered lines: 11
Uncovered lines: 313
Coverable lines: 324
Total lines: 675
Line coverage: 3.3%
Branch coverage
2%
Covered branches: 4
Total branches: 144
Branch coverage: 2.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 4/30/2026 - 12:14:58 AM Line coverage: 4.2% (14/331) Branch coverage: 3.4% (5/146) Total lines: 6885/4/2026 - 12:15:16 AM Line coverage: 3.3% (11/331) Branch coverage: 2.7% (4/146) Total lines: 6888/2/2026 - 12:17:28 AM Line coverage: 3.3% (11/324) Branch coverage: 2.7% (4/144) Total lines: 675 4/30/2026 - 12:14:58 AM Line coverage: 4.2% (14/331) Branch coverage: 3.4% (5/146) Total lines: 6885/4/2026 - 12:15:16 AM Line coverage: 3.3% (11/331) Branch coverage: 2.7% (4/146) Total lines: 6888/2/2026 - 12:17:28 AM Line coverage: 3.3% (11/324) Branch coverage: 2.7% (4/144) Total lines: 675

Coverage delta

Coverage delta 1 -1

Metrics

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/Playlists/PlaylistManager.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Globalization;
 8using System.IO;
 9using System.Linq;
 10using System.Threading;
 11using System.Threading.Tasks;
 12using Jellyfin.Data.Enums;
 13using Jellyfin.Database.Implementations.Entities;
 14using Jellyfin.Extensions;
 15using MediaBrowser.Controller.Dto;
 16using MediaBrowser.Controller.Entities;
 17using MediaBrowser.Controller.Entities.Audio;
 18using MediaBrowser.Controller.Extensions;
 19using MediaBrowser.Controller.Library;
 20using MediaBrowser.Controller.Playlists;
 21using MediaBrowser.Controller.Providers;
 22using MediaBrowser.Model.Entities;
 23using MediaBrowser.Model.IO;
 24using MediaBrowser.Model.Playlists;
 25using Microsoft.Extensions.Configuration;
 26using Microsoft.Extensions.Logging;
 27using PlaylistsNET.Content;
 28using PlaylistsNET.Models;
 29using Genre = MediaBrowser.Controller.Entities.Genre;
 30using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
 31
 32namespace Emby.Server.Implementations.Playlists
 33{
 34    public class PlaylistManager : IPlaylistManager
 35    {
 36        private readonly ILibraryManager _libraryManager;
 37        private readonly IFileSystem _fileSystem;
 38        private readonly ILibraryMonitor _iLibraryMonitor;
 39        private readonly ILogger<PlaylistManager> _logger;
 40        private readonly IUserManager _userManager;
 41        private readonly IProviderManager _providerManager;
 42        private readonly IConfiguration _appConfig;
 43
 44        public PlaylistManager(
 45            ILibraryManager libraryManager,
 46            IFileSystem fileSystem,
 47            ILibraryMonitor iLibraryMonitor,
 48            ILogger<PlaylistManager> logger,
 49            IUserManager userManager,
 50            IProviderManager providerManager,
 51            IConfiguration appConfig)
 52        {
 1653            _libraryManager = libraryManager;
 1654            _fileSystem = fileSystem;
 1655            _iLibraryMonitor = iLibraryMonitor;
 1656            _logger = logger;
 1657            _userManager = userManager;
 1658            _providerManager = providerManager;
 1659            _appConfig = appConfig;
 1660        }
 61
 62        public Playlist GetPlaylistForUser(Guid playlistId, Guid userId)
 63        {
 064            return GetPlaylists(userId).Where(p => p.Id.Equals(playlistId)).FirstOrDefault();
 65        }
 66
 67        public IEnumerable<Playlist> GetPlaylists(Guid userId)
 68        {
 069            var user = _userManager.GetUserById(userId);
 070            return _libraryManager.GetItemList(new InternalItemsQuery
 071            {
 072                IncludeItemTypes = [BaseItemKind.Playlist],
 073                Recursive = true,
 074                DtoOptions = new DtoOptions(false)
 075            })
 076            .Cast<Playlist>()
 077            .Where(p => p.IsVisible(user));
 78        }
 79
 80        public async Task<PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest request)
 81        {
 082            var name = request.Name;
 083            var folderName = _fileSystem.GetValidFilename(name);
 084            var parentFolder = GetPlaylistsFolder(request.UserId);
 085            if (parentFolder is null)
 86            {
 087                throw new ArgumentException(nameof(parentFolder));
 88            }
 89
 090            if (request.MediaType is null || request.MediaType == MediaType.Unknown)
 91            {
 092                foreach (var itemId in request.ItemIdList)
 93                {
 094                    var item = _libraryManager.GetItemById(itemId) ?? throw new ArgumentException("No item exists with t
 095                    if (item.MediaType != MediaType.Unknown)
 96                    {
 097                        request.MediaType = item.MediaType;
 98                    }
 099                    else if (item is MusicArtist || item is MusicAlbum || item is MusicGenre)
 100                    {
 0101                        request.MediaType = MediaType.Audio;
 102                    }
 0103                    else if (item is Genre)
 104                    {
 0105                        request.MediaType = MediaType.Video;
 106                    }
 107                    else
 108                    {
 0109                        if (item is Folder folder)
 110                        {
 0111                            request.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlayli
 0112                                .Select(i => i.MediaType)
 0113                                .FirstOrDefault(i => i != MediaType.Unknown);
 114                        }
 115                    }
 116
 0117                    if (request.MediaType is not null && request.MediaType != MediaType.Unknown)
 118                    {
 119                        break;
 120                    }
 121                }
 122            }
 123
 0124            if (request.MediaType is null || request.MediaType == MediaType.Unknown)
 125            {
 0126                request.MediaType = MediaType.Audio;
 127            }
 128
 0129            var user = _userManager.GetUserById(request.UserId);
 0130            var path = Path.Combine(parentFolder.Path, folderName);
 0131            path = GetTargetPath(path);
 132
 0133            _iLibraryMonitor.ReportFileSystemChangeBeginning(path);
 134
 135            try
 136            {
 0137                var info = Directory.CreateDirectory(path);
 0138                var playlist = new Playlist
 0139                {
 0140                    Name = name,
 0141                    Path = path,
 0142                    OwnerUserId = request.UserId,
 0143                    Shares = request.Users ?? [],
 0144                    OpenAccess = request.Public ?? false,
 0145                    DateCreated = info.CreationTimeUtc,
 0146                    DateModified = info.LastWriteTimeUtc
 0147                };
 148
 0149                playlist.SetMediaType(request.MediaType);
 0150                parentFolder.AddChild(playlist);
 151
 0152                await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave
 0153                    .ConfigureAwait(false);
 154
 0155                if (request.ItemIdList.Count > 0)
 156                {
 0157                    await AddToPlaylistInternal(playlist.Id, request.ItemIdList, user, new DtoOptions(false)
 0158                    {
 0159                        EnableImages = true
 0160                    }).ConfigureAwait(false);
 161                }
 162
 0163                return new PlaylistCreationResult(playlist.Id.ToString("N", CultureInfo.InvariantCulture));
 164            }
 165            finally
 166            {
 167                // Refresh handled internally
 0168                _iLibraryMonitor.ReportFileSystemChangeComplete(path, false);
 169            }
 0170        }
 171
 172        private List<Playlist> GetUserPlaylists(Guid userId)
 173        {
 0174            var user = _userManager.GetUserById(userId);
 0175            var playlistsFolder = GetPlaylistsFolder(userId);
 0176            if (playlistsFolder is null)
 177            {
 0178                return [];
 179            }
 180
 0181            return playlistsFolder.GetChildren(user, true).OfType<Playlist>().ToList();
 182        }
 183
 184        private static string GetTargetPath(string path)
 185        {
 0186            while (Directory.Exists(path))
 187            {
 0188                path += "1";
 189            }
 190
 0191            return path;
 192        }
 193
 194        private IReadOnlyList<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, User user, DtoOptions options)
 195        {
 0196            var items = itemIds.Select(_libraryManager.GetItemById).Where(i => i is not null);
 197
 0198            return Playlist.GetPlaylistItems(items, user, options);
 199        }
 200
 201        public Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, int? position, Guid userI
 202        {
 0203            var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
 204
 0205            return AddToPlaylistInternal(
 0206                playlistId,
 0207                itemIds,
 0208                user,
 0209                new DtoOptions(false)
 0210                {
 0211                    EnableImages = true
 0212                },
 0213                position);
 214        }
 215
 216        private async Task AddToPlaylistInternal(Guid playlistId, IReadOnlyCollection<Guid> newItemIds, User user, DtoOp
 217        {
 218            // Retrieve the existing playlist
 0219            var playlist = _libraryManager.GetItemById(playlistId) as Playlist
 0220                ?? throw new ArgumentException("No Playlist exists with Id " + playlistId);
 221
 222            // Retrieve all the items to be added to the playlist.
 0223            var newItems = GetPlaylistItems(newItemIds, user, options)
 0224                .Where(i => i.SupportsAddingToPlaylist);
 225
 226            // Create a list of the new linked children to add to the playlist
 0227            var childrenToAdd = newItems
 0228                .Select(LinkedChild.Create)
 0229                .ToList();
 230
 231            // Do nothing else if there are no items to add to the playlist
 0232            if (childrenToAdd.Count == 0)
 233            {
 0234                return;
 235            }
 236
 237            // Update the playlist in the repository
 0238            if (position.HasValue)
 239            {
 0240                if (position.Value <= 0)
 241                {
 0242                    playlist.LinkedChildren = [.. childrenToAdd, .. playlist.LinkedChildren];
 243                }
 0244                else if (position.Value >= playlist.LinkedChildren.Length)
 245                {
 0246                    playlist.LinkedChildren = [.. playlist.LinkedChildren, .. childrenToAdd];
 247                }
 248                else
 249                {
 0250                    playlist.LinkedChildren = [
 0251                        .. playlist.LinkedChildren[0..position.Value],
 0252                        .. childrenToAdd,
 0253                        .. playlist.LinkedChildren[position.Value..playlist.LinkedChildren.Length]
 0254                    ];
 255                }
 256            }
 257            else
 258            {
 0259                playlist.LinkedChildren = [.. playlist.LinkedChildren, .. childrenToAdd];
 260            }
 261
 0262            playlist.DateLastMediaAdded = DateTime.UtcNow;
 263
 0264            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 265
 266            // Refresh playlist metadata
 0267            _providerManager.QueueRefresh(
 0268                playlist.Id,
 0269                new MetadataRefreshOptions(new DirectoryService(_fileSystem))
 0270                {
 0271                    ForceSave = true
 0272                },
 0273                RefreshPriority.High);
 0274        }
 275
 276        public async Task RemoveItemFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds)
 277        {
 0278            if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
 279            {
 0280                throw new ArgumentException("No Playlist exists with the supplied Id");
 281            }
 282
 0283            var children = playlist.GetManageableItems().ToList();
 284
 0285            var idList = entryIds.ToList();
 286
 0287            var removals = children.Where(i => idList.Contains(i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCultur
 288
 0289            playlist.LinkedChildren = children.Except(removals)
 0290                .Select(i => i.Item1)
 0291                .ToArray();
 292
 0293            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 294
 0295            _providerManager.QueueRefresh(
 0296                playlist.Id,
 0297                new MetadataRefreshOptions(new DirectoryService(_fileSystem))
 0298                {
 0299                    ForceSave = true
 0300                },
 0301                RefreshPriority.High);
 0302        }
 303
 304        internal static int DetermineAdjustedIndex(int newPriorIndexAllChildren, int newIndex)
 305        {
 3306            if (newIndex == 0)
 307            {
 2308                return newPriorIndexAllChildren > 0 ? newPriorIndexAllChildren - 1 : 0;
 309            }
 310
 1311            return newPriorIndexAllChildren + 1;
 312        }
 313
 314        public async Task MoveItemAsync(string playlistId, string entryId, int newIndex, Guid callingUserId)
 315        {
 0316            if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
 317            {
 0318                throw new ArgumentException("No Playlist exists with the supplied Id");
 319            }
 320
 0321            var user = _userManager.GetUserById(callingUserId);
 0322            var children = playlist.GetManageableItems().ToList();
 0323            var accessibleChildren = children.Where(c => c.Item2.IsVisible(user)).ToArray();
 324
 0325            var oldIndexAll = children.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("N", CultureInfo.I
 0326            var oldIndexAccessible = accessibleChildren.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("
 327
 0328            if (oldIndexAccessible == newIndex)
 329            {
 0330                return;
 331            }
 332
 0333            var newPriorItemIndex = Math.Max(newIndex - 1, 0);
 0334            var newPriorItemId = accessibleChildren[newPriorItemIndex].Item1.ItemId;
 0335            var newPriorItemIndexOnAllChildren = children.FindIndex(c => c.Item1.ItemId.Equals(newPriorItemId));
 0336            var adjustedNewIndex = DetermineAdjustedIndex(newPriorItemIndexOnAllChildren, newIndex);
 337
 0338            var item = playlist.LinkedChildren.FirstOrDefault(i => string.Equals(entryId, i.ItemId?.ToString("N", Cultur
 0339            if (item is null)
 340            {
 0341                _logger.LogWarning("Modified item not found in playlist. ItemId: {ItemId}, PlaylistId: {PlaylistId}", en
 342
 0343                return;
 344            }
 345
 0346            var newList = playlist.LinkedChildren.ToList();
 0347            newList.Remove(item);
 348
 0349            if (newIndex >= newList.Count)
 350            {
 0351                newList.Add(item);
 352            }
 353            else
 354            {
 0355                newList.Insert(adjustedNewIndex, item);
 356            }
 357
 0358            playlist.LinkedChildren = [.. newList];
 359
 0360            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 0361        }
 362
 363        /// <inheritdoc />
 364        public void SavePlaylistFile(Playlist item)
 365        {
 366            // this is probably best done as a metadata provider
 367            // saving a file over itself will require some work to prevent this from happening when not needed
 0368            var playlistPath = item.Path;
 0369            var extension = Path.GetExtension(playlistPath.AsSpan());
 370
 0371            if (extension.Equals(".wpl", StringComparison.OrdinalIgnoreCase))
 372            {
 0373                var playlist = new WplPlaylist();
 0374                foreach (var child in item.GetLinkedChildren())
 375                {
 0376                    var entry = new WplPlaylistEntry()
 0377                    {
 0378                        Path = NormalizeItemPath(playlistPath, child.Path),
 0379                        TrackTitle = child.Name,
 0380                        AlbumTitle = child.Album
 0381                    };
 382
 0383                    if (child is IHasAlbumArtist hasAlbumArtist)
 384                    {
 0385                        entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : nul
 386                    }
 387
 0388                    if (child is IHasArtist hasArtist)
 389                    {
 0390                        entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null;
 391                    }
 392
 0393                    if (child.RunTimeTicks.HasValue)
 394                    {
 0395                        entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value);
 396                    }
 397
 0398                    playlist.PlaylistEntries.Add(entry);
 399                }
 400
 0401                string text = new WplContent().ToText(playlist);
 0402                File.WriteAllText(playlistPath, text);
 403            }
 0404            else if (extension.Equals(".zpl", StringComparison.OrdinalIgnoreCase))
 405            {
 0406                var playlist = new ZplPlaylist();
 0407                foreach (var child in item.GetLinkedChildren())
 408                {
 0409                    var entry = new ZplPlaylistEntry()
 0410                    {
 0411                        Path = NormalizeItemPath(playlistPath, child.Path),
 0412                        TrackTitle = child.Name,
 0413                        AlbumTitle = child.Album
 0414                    };
 415
 0416                    if (child is IHasAlbumArtist hasAlbumArtist)
 417                    {
 0418                        entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : nul
 419                    }
 420
 0421                    if (child is IHasArtist hasArtist)
 422                    {
 0423                        entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null;
 424                    }
 425
 0426                    if (child.RunTimeTicks.HasValue)
 427                    {
 0428                        entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value);
 429                    }
 430
 0431                    playlist.PlaylistEntries.Add(entry);
 432                }
 433
 0434                string text = new ZplContent().ToText(playlist);
 0435                File.WriteAllText(playlistPath, text);
 436            }
 0437            else if (extension.Equals(".m3u", StringComparison.OrdinalIgnoreCase))
 438            {
 0439                var playlist = new M3uPlaylist
 0440                {
 0441                    IsExtended = true
 0442                };
 0443                foreach (var child in item.GetLinkedChildren())
 444                {
 0445                    var entry = new M3uPlaylistEntry()
 0446                    {
 0447                        Path = NormalizeItemPath(playlistPath, child.Path),
 0448                        Title = child.Name,
 0449                        Album = child.Album
 0450                    };
 451
 0452                    if (child is IHasAlbumArtist hasAlbumArtist)
 453                    {
 0454                        entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : nul
 455                    }
 456
 0457                    if (child.RunTimeTicks.HasValue)
 458                    {
 0459                        entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value);
 460                    }
 461
 0462                    playlist.PlaylistEntries.Add(entry);
 463                }
 464
 0465                string text = new M3uContent().ToText(playlist);
 0466                File.WriteAllText(playlistPath, text);
 467            }
 0468            else if (extension.Equals(".m3u8", StringComparison.OrdinalIgnoreCase))
 469            {
 0470                var playlist = new M3uPlaylist
 0471                {
 0472                    IsExtended = true
 0473                };
 474
 0475                foreach (var child in item.GetLinkedChildren())
 476                {
 0477                    var entry = new M3uPlaylistEntry()
 0478                    {
 0479                        Path = NormalizeItemPath(playlistPath, child.Path),
 0480                        Title = child.Name,
 0481                        Album = child.Album
 0482                    };
 483
 0484                    if (child is IHasAlbumArtist hasAlbumArtist)
 485                    {
 0486                        entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : nul
 487                    }
 488
 0489                    if (child.RunTimeTicks.HasValue)
 490                    {
 0491                        entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value);
 492                    }
 493
 0494                    playlist.PlaylistEntries.Add(entry);
 495                }
 496
 0497                string text = new M3uContent().ToText(playlist);
 0498                File.WriteAllText(playlistPath, text);
 499            }
 0500            else if (extension.Equals(".pls", StringComparison.OrdinalIgnoreCase))
 501            {
 0502                var playlist = new PlsPlaylist();
 0503                foreach (var child in item.GetLinkedChildren())
 504                {
 0505                    var entry = new PlsPlaylistEntry()
 0506                    {
 0507                        Path = NormalizeItemPath(playlistPath, child.Path),
 0508                        Title = child.Name
 0509                    };
 510
 0511                    if (child.RunTimeTicks.HasValue)
 512                    {
 0513                        entry.Length = TimeSpan.FromTicks(child.RunTimeTicks.Value);
 514                    }
 515
 0516                    playlist.PlaylistEntries.Add(entry);
 517                }
 518
 0519                string text = new PlsContent().ToText(playlist);
 0520                File.WriteAllText(playlistPath, text);
 521            }
 0522        }
 523
 524        private static string NormalizeItemPath(string playlistPath, string itemPath)
 525        {
 0526            return MakeRelativePath(Path.GetDirectoryName(playlistPath), itemPath);
 527        }
 528
 529        private static string MakeRelativePath(string folderPath, string fileAbsolutePath)
 530        {
 0531            ArgumentException.ThrowIfNullOrEmpty(folderPath);
 0532            ArgumentException.ThrowIfNullOrEmpty(fileAbsolutePath);
 533
 0534            if (!folderPath.EndsWith(Path.DirectorySeparatorChar))
 535            {
 0536                folderPath += Path.DirectorySeparatorChar;
 537            }
 538
 0539            var folderUri = new Uri(folderPath);
 0540            var fileAbsoluteUri = new Uri(fileAbsolutePath);
 541
 542            // path can't be made relative
 0543            if (folderUri.Scheme != fileAbsoluteUri.Scheme)
 544            {
 0545                return fileAbsolutePath;
 546            }
 547
 0548            var relativeUri = folderUri.MakeRelativeUri(fileAbsoluteUri);
 0549            string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
 550
 0551            if (fileAbsoluteUri.Scheme.Equals("file", StringComparison.OrdinalIgnoreCase))
 552            {
 0553                relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
 554            }
 555
 0556            return relativePath;
 557        }
 558
 559        /// <inheritdoc />
 560        public Folder GetPlaylistsFolder()
 561        {
 0562            return GetPlaylistsFolder(Guid.Empty);
 563        }
 564
 565        /// <inheritdoc />
 566        public Folder GetPlaylistsFolder(Guid userId)
 567        {
 568            const string TypeName = "PlaylistsFolder";
 569
 0570            return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Na
 0571                _libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetTyp
 572        }
 573
 574        /// <inheritdoc />
 575        public async Task RemovePlaylistsAsync(Guid userId)
 576        {
 0577            var playlists = GetUserPlaylists(userId);
 0578            foreach (var playlist in playlists)
 579            {
 580                // Update owner if shared
 0581                var rankedShares = playlist.Shares.OrderByDescending(x => x.CanEdit).ToList();
 0582                if (rankedShares.Count > 0)
 583                {
 0584                    playlist.OwnerUserId = rankedShares[0].UserId;
 0585                    playlist.Shares = rankedShares.Skip(1).ToArray();
 0586                    await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 587                }
 0588                else if (!playlist.OpenAccess)
 589                {
 590                    // Remove playlist if not shared
 0591                    _libraryManager.DeleteItem(
 0592                        playlist,
 0593                        new DeleteOptions
 0594                        {
 0595                            DeleteFileLocation = false,
 0596                            DeleteFromExternalProvider = false
 0597                        },
 0598                        playlist.GetParent(),
 0599                        false);
 600                }
 601            }
 0602        }
 603
 604        public async Task UpdatePlaylist(PlaylistUpdateRequest request)
 605        {
 0606            var playlist = GetPlaylistForUser(request.Id, request.UserId);
 607
 0608            if (request.Ids is not null)
 609            {
 0610                playlist.LinkedChildren = [];
 0611                await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 612
 0613                var user = _userManager.GetUserById(request.UserId);
 0614                await AddToPlaylistInternal(request.Id, request.Ids, user, new DtoOptions(false)
 0615                {
 0616                    EnableImages = true
 0617                }).ConfigureAwait(false);
 618
 0619                playlist = GetPlaylistForUser(request.Id, request.UserId);
 620            }
 621
 0622            if (request.Name is not null)
 623            {
 0624                playlist.Name = request.Name;
 625            }
 626
 0627            if (request.Users is not null)
 628            {
 0629                playlist.Shares = request.Users;
 630            }
 631
 0632            if (request.Public is not null)
 633            {
 0634                playlist.OpenAccess = request.Public.Value;
 635            }
 636
 0637            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 0638        }
 639
 640        public async Task AddUserToShares(PlaylistUserUpdateRequest request)
 641        {
 0642            var userId = request.UserId;
 0643            var playlist = GetPlaylistForUser(request.Id, userId);
 0644            var shares = playlist.Shares.ToList();
 0645            var existingUserShare = shares.FirstOrDefault(s => s.UserId.Equals(userId));
 0646            if (existingUserShare is not null)
 647            {
 0648                shares.Remove(existingUserShare);
 649            }
 650
 0651            shares.Add(new PlaylistUserPermissions(userId, request.CanEdit ?? false));
 0652            playlist.Shares = shares;
 0653            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 0654        }
 655
 656        public async Task RemoveUserFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share)
 657        {
 0658            var playlist = GetPlaylistForUser(playlistId, userId);
 0659            var shares = playlist.Shares.ToList();
 0660            shares.Remove(share);
 0661            playlist.Shares = shares;
 0662            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 0663        }
 664
 665        private async Task UpdatePlaylistInternal(Playlist playlist)
 666        {
 0667            await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(f
 668
 0669            if (playlist.IsFile)
 670            {
 0671                SavePlaylistFile(playlist);
 672            }
 0673        }
 674    }
 675}