< 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
9%
Covered lines: 14
Uncovered lines: 129
Coverable lines: 143
Total lines: 657
Line coverage: 9.7%
Branch coverage
6%
Covered branches: 5
Total branches: 72
Branch coverage: 6.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetPlaylistForUser(...)100%210%
GetPlaylists(...)100%210%
GetUserPlaylists(...)0%620%
GetTargetPath(...)0%620%
GetPlaylistItems(...)100%210%
AddItemToPlaylistAsync(...)0%620%
DetermineAdjustedIndex(...)100%44100%
SavePlaylistFile(...)0%2970540%
NormalizeItemPath(...)100%210%
MakeRelativePath(...)0%4260%
GetPlaylistsFolder()100%11100%
GetPlaylistsFolder(...)50%22100%

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        {
 2153            _libraryManager = libraryManager;
 2154            _fileSystem = fileSystem;
 2155            _iLibraryMonitor = iLibraryMonitor;
 2156            _logger = logger;
 2157            _userManager = userManager;
 2158            _providerManager = providerManager;
 2159            _appConfig = appConfig;
 2160        }
 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        {
 82            var name = request.Name;
 83            var folderName = _fileSystem.GetValidFilename(name);
 84            var parentFolder = GetPlaylistsFolder(request.UserId);
 85            if (parentFolder is null)
 86            {
 87                throw new ArgumentException(nameof(parentFolder));
 88            }
 89
 90            if (request.MediaType is null || request.MediaType == MediaType.Unknown)
 91            {
 92                foreach (var itemId in request.ItemIdList)
 93                {
 94                    var item = _libraryManager.GetItemById(itemId) ?? throw new ArgumentException("No item exists with t
 95                    if (item.MediaType != MediaType.Unknown)
 96                    {
 97                        request.MediaType = item.MediaType;
 98                    }
 99                    else if (item is MusicArtist || item is MusicAlbum || item is MusicGenre)
 100                    {
 101                        request.MediaType = MediaType.Audio;
 102                    }
 103                    else if (item is Genre)
 104                    {
 105                        request.MediaType = MediaType.Video;
 106                    }
 107                    else
 108                    {
 109                        if (item is Folder folder)
 110                        {
 111                            request.MediaType = folder.GetRecursiveChildren(i => !i.IsFolder && i.SupportsAddingToPlayli
 112                                .Select(i => i.MediaType)
 113                                .FirstOrDefault(i => i != MediaType.Unknown);
 114                        }
 115                    }
 116
 117                    if (request.MediaType is not null && request.MediaType != MediaType.Unknown)
 118                    {
 119                        break;
 120                    }
 121                }
 122            }
 123
 124            if (request.MediaType is null || request.MediaType == MediaType.Unknown)
 125            {
 126                request.MediaType = MediaType.Audio;
 127            }
 128
 129            var user = _userManager.GetUserById(request.UserId);
 130            var path = Path.Combine(parentFolder.Path, folderName);
 131            path = GetTargetPath(path);
 132
 133            _iLibraryMonitor.ReportFileSystemChangeBeginning(path);
 134
 135            try
 136            {
 137                Directory.CreateDirectory(path);
 138                var playlist = new Playlist
 139                {
 140                    Name = name,
 141                    Path = path,
 142                    OwnerUserId = request.UserId,
 143                    Shares = request.Users ?? [],
 144                    OpenAccess = request.Public ?? false
 145                };
 146
 147                playlist.SetMediaType(request.MediaType);
 148                parentFolder.AddChild(playlist);
 149
 150                await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave
 151                    .ConfigureAwait(false);
 152
 153                if (request.ItemIdList.Count > 0)
 154                {
 155                    await AddToPlaylistInternal(playlist.Id, request.ItemIdList, user, new DtoOptions(false)
 156                    {
 157                        EnableImages = true
 158                    }).ConfigureAwait(false);
 159                }
 160
 161                return new PlaylistCreationResult(playlist.Id.ToString("N", CultureInfo.InvariantCulture));
 162            }
 163            finally
 164            {
 165                // Refresh handled internally
 166                _iLibraryMonitor.ReportFileSystemChangeComplete(path, false);
 167            }
 168        }
 169
 170        private List<Playlist> GetUserPlaylists(Guid userId)
 171        {
 0172            var user = _userManager.GetUserById(userId);
 0173            var playlistsFolder = GetPlaylistsFolder(userId);
 0174            if (playlistsFolder is null)
 175            {
 0176                return [];
 177            }
 178
 0179            return playlistsFolder.GetChildren(user, true).OfType<Playlist>().ToList();
 180        }
 181
 182        private static string GetTargetPath(string path)
 183        {
 0184            while (Directory.Exists(path))
 185            {
 0186                path += "1";
 187            }
 188
 0189            return path;
 190        }
 191
 192        private IReadOnlyList<BaseItem> GetPlaylistItems(IEnumerable<Guid> itemIds, User user, DtoOptions options)
 193        {
 0194            var items = itemIds.Select(_libraryManager.GetItemById).Where(i => i is not null);
 195
 0196            return Playlist.GetPlaylistItems(items, user, options);
 197        }
 198
 199        public Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId)
 200        {
 0201            var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
 202
 0203            return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false)
 0204            {
 0205                EnableImages = true
 0206            });
 207        }
 208
 209        private async Task AddToPlaylistInternal(Guid playlistId, IReadOnlyCollection<Guid> newItemIds, User user, DtoOp
 210        {
 211            // Retrieve the existing playlist
 212            var playlist = _libraryManager.GetItemById(playlistId) as Playlist
 213                ?? throw new ArgumentException("No Playlist exists with Id " + playlistId);
 214
 215            // Retrieve all the items to be added to the playlist
 216            var newItems = GetPlaylistItems(newItemIds, user, options)
 217                .Where(i => i.SupportsAddingToPlaylist);
 218
 219            // Filter out duplicate items
 220            var existingIds = playlist.LinkedChildren.Select(c => c.ItemId).ToHashSet();
 221            newItems = newItems
 222                .Where(i => !existingIds.Contains(i.Id))
 223                .Distinct();
 224
 225            // Create a list of the new linked children to add to the playlist
 226            var childrenToAdd = newItems
 227                .Select(LinkedChild.Create)
 228                .ToList();
 229
 230            // Log duplicates that have been ignored, if any
 231            int numDuplicates = newItemIds.Count - childrenToAdd.Count;
 232            if (numDuplicates > 0)
 233            {
 234                _logger.LogWarning("Ignored adding {DuplicateCount} duplicate items to playlist {PlaylistName}.", numDup
 235            }
 236
 237            // Do nothing else if there are no items to add to the playlist
 238            if (childrenToAdd.Count == 0)
 239            {
 240                return;
 241            }
 242
 243            // Update the playlist in the repository
 244            playlist.LinkedChildren = [.. playlist.LinkedChildren, .. childrenToAdd];
 245
 246            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 247
 248            // Refresh playlist metadata
 249            _providerManager.QueueRefresh(
 250                playlist.Id,
 251                new MetadataRefreshOptions(new DirectoryService(_fileSystem))
 252                {
 253                    ForceSave = true
 254                },
 255                RefreshPriority.High);
 256        }
 257
 258        public async Task RemoveItemFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds)
 259        {
 260            if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
 261            {
 262                throw new ArgumentException("No Playlist exists with the supplied Id");
 263            }
 264
 265            var children = playlist.GetManageableItems().ToList();
 266
 267            var idList = entryIds.ToList();
 268
 269            var removals = children.Where(i => idList.Contains(i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCultur
 270
 271            playlist.LinkedChildren = children.Except(removals)
 272                .Select(i => i.Item1)
 273                .ToArray();
 274
 275            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 276
 277            _providerManager.QueueRefresh(
 278                playlist.Id,
 279                new MetadataRefreshOptions(new DirectoryService(_fileSystem))
 280                {
 281                    ForceSave = true
 282                },
 283                RefreshPriority.High);
 284        }
 285
 286        internal static int DetermineAdjustedIndex(int newPriorIndexAllChildren, int newIndex)
 287        {
 3288            if (newIndex == 0)
 289            {
 2290                return newPriorIndexAllChildren > 0 ? newPriorIndexAllChildren - 1 : 0;
 291            }
 292
 1293            return newPriorIndexAllChildren + 1;
 294        }
 295
 296        public async Task MoveItemAsync(string playlistId, string entryId, int newIndex, Guid callingUserId)
 297        {
 298            if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
 299            {
 300                throw new ArgumentException("No Playlist exists with the supplied Id");
 301            }
 302
 303            var user = _userManager.GetUserById(callingUserId);
 304            var children = playlist.GetManageableItems().ToList();
 305            var accessibleChildren = children.Where(c => c.Item2.IsVisible(user)).ToArray();
 306
 307            var oldIndexAll = children.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("N", CultureInfo.I
 308            var oldIndexAccessible = accessibleChildren.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("
 309
 310            if (oldIndexAccessible == newIndex)
 311            {
 312                return;
 313            }
 314
 315            var newPriorItemIndex = newIndex > oldIndexAccessible ? newIndex : newIndex - 1 < 0 ? 0 : newIndex - 1;
 316            var newPriorItemId = accessibleChildren[newPriorItemIndex].Item1.ItemId;
 317            var newPriorItemIndexOnAllChildren = children.FindIndex(c => c.Item1.ItemId.Equals(newPriorItemId));
 318            var adjustedNewIndex = DetermineAdjustedIndex(newPriorItemIndexOnAllChildren, newIndex);
 319
 320            var item = playlist.LinkedChildren.FirstOrDefault(i => string.Equals(entryId, i.ItemId?.ToString("N", Cultur
 321            if (item is null)
 322            {
 323                _logger.LogWarning("Modified item not found in playlist. ItemId: {ItemId}, PlaylistId: {PlaylistId}", en
 324
 325                return;
 326            }
 327
 328            var newList = playlist.LinkedChildren.ToList();
 329            newList.Remove(item);
 330
 331            if (newIndex >= newList.Count)
 332            {
 333                newList.Add(item);
 334            }
 335            else
 336            {
 337                newList.Insert(adjustedNewIndex, item);
 338            }
 339
 340            playlist.LinkedChildren = [.. newList];
 341
 342            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 343        }
 344
 345        /// <inheritdoc />
 346        public void SavePlaylistFile(Playlist item)
 347        {
 348            // this is probably best done as a metadata provider
 349            // saving a file over itself will require some work to prevent this from happening when not needed
 0350            var playlistPath = item.Path;
 0351            var extension = Path.GetExtension(playlistPath.AsSpan());
 352
 0353            if (extension.Equals(".wpl", StringComparison.OrdinalIgnoreCase))
 354            {
 0355                var playlist = new WplPlaylist();
 0356                foreach (var child in item.GetLinkedChildren())
 357                {
 0358                    var entry = new WplPlaylistEntry()
 0359                    {
 0360                        Path = NormalizeItemPath(playlistPath, child.Path),
 0361                        TrackTitle = child.Name,
 0362                        AlbumTitle = child.Album
 0363                    };
 364
 0365                    if (child is IHasAlbumArtist hasAlbumArtist)
 366                    {
 0367                        entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : nul
 368                    }
 369
 0370                    if (child is IHasArtist hasArtist)
 371                    {
 0372                        entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null;
 373                    }
 374
 0375                    if (child.RunTimeTicks.HasValue)
 376                    {
 0377                        entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value);
 378                    }
 379
 0380                    playlist.PlaylistEntries.Add(entry);
 381                }
 382
 0383                string text = new WplContent().ToText(playlist);
 0384                File.WriteAllText(playlistPath, text);
 385            }
 0386            else if (extension.Equals(".zpl", StringComparison.OrdinalIgnoreCase))
 387            {
 0388                var playlist = new ZplPlaylist();
 0389                foreach (var child in item.GetLinkedChildren())
 390                {
 0391                    var entry = new ZplPlaylistEntry()
 0392                    {
 0393                        Path = NormalizeItemPath(playlistPath, child.Path),
 0394                        TrackTitle = child.Name,
 0395                        AlbumTitle = child.Album
 0396                    };
 397
 0398                    if (child is IHasAlbumArtist hasAlbumArtist)
 399                    {
 0400                        entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : nul
 401                    }
 402
 0403                    if (child is IHasArtist hasArtist)
 404                    {
 0405                        entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null;
 406                    }
 407
 0408                    if (child.RunTimeTicks.HasValue)
 409                    {
 0410                        entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value);
 411                    }
 412
 0413                    playlist.PlaylistEntries.Add(entry);
 414                }
 415
 0416                string text = new ZplContent().ToText(playlist);
 0417                File.WriteAllText(playlistPath, text);
 418            }
 0419            else if (extension.Equals(".m3u", StringComparison.OrdinalIgnoreCase))
 420            {
 0421                var playlist = new M3uPlaylist
 0422                {
 0423                    IsExtended = true
 0424                };
 0425                foreach (var child in item.GetLinkedChildren())
 426                {
 0427                    var entry = new M3uPlaylistEntry()
 0428                    {
 0429                        Path = NormalizeItemPath(playlistPath, child.Path),
 0430                        Title = child.Name,
 0431                        Album = child.Album
 0432                    };
 433
 0434                    if (child is IHasAlbumArtist hasAlbumArtist)
 435                    {
 0436                        entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : nul
 437                    }
 438
 0439                    if (child.RunTimeTicks.HasValue)
 440                    {
 0441                        entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value);
 442                    }
 443
 0444                    playlist.PlaylistEntries.Add(entry);
 445                }
 446
 0447                string text = new M3uContent().ToText(playlist);
 0448                File.WriteAllText(playlistPath, text);
 449            }
 0450            else if (extension.Equals(".m3u8", StringComparison.OrdinalIgnoreCase))
 451            {
 0452                var playlist = new M3uPlaylist
 0453                {
 0454                    IsExtended = true
 0455                };
 456
 0457                foreach (var child in item.GetLinkedChildren())
 458                {
 0459                    var entry = new M3uPlaylistEntry()
 0460                    {
 0461                        Path = NormalizeItemPath(playlistPath, child.Path),
 0462                        Title = child.Name,
 0463                        Album = child.Album
 0464                    };
 465
 0466                    if (child is IHasAlbumArtist hasAlbumArtist)
 467                    {
 0468                        entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : nul
 469                    }
 470
 0471                    if (child.RunTimeTicks.HasValue)
 472                    {
 0473                        entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value);
 474                    }
 475
 0476                    playlist.PlaylistEntries.Add(entry);
 477                }
 478
 0479                string text = new M3uContent().ToText(playlist);
 0480                File.WriteAllText(playlistPath, text);
 481            }
 0482            else if (extension.Equals(".pls", StringComparison.OrdinalIgnoreCase))
 483            {
 0484                var playlist = new PlsPlaylist();
 0485                foreach (var child in item.GetLinkedChildren())
 486                {
 0487                    var entry = new PlsPlaylistEntry()
 0488                    {
 0489                        Path = NormalizeItemPath(playlistPath, child.Path),
 0490                        Title = child.Name
 0491                    };
 492
 0493                    if (child.RunTimeTicks.HasValue)
 494                    {
 0495                        entry.Length = TimeSpan.FromTicks(child.RunTimeTicks.Value);
 496                    }
 497
 0498                    playlist.PlaylistEntries.Add(entry);
 499                }
 500
 0501                string text = new PlsContent().ToText(playlist);
 0502                File.WriteAllText(playlistPath, text);
 503            }
 0504        }
 505
 506        private static string NormalizeItemPath(string playlistPath, string itemPath)
 507        {
 0508            return MakeRelativePath(Path.GetDirectoryName(playlistPath), itemPath);
 509        }
 510
 511        private static string MakeRelativePath(string folderPath, string fileAbsolutePath)
 512        {
 0513            ArgumentException.ThrowIfNullOrEmpty(folderPath);
 0514            ArgumentException.ThrowIfNullOrEmpty(fileAbsolutePath);
 515
 0516            if (!folderPath.EndsWith(Path.DirectorySeparatorChar))
 517            {
 0518                folderPath += Path.DirectorySeparatorChar;
 519            }
 520
 0521            var folderUri = new Uri(folderPath);
 0522            var fileAbsoluteUri = new Uri(fileAbsolutePath);
 523
 524            // path can't be made relative
 0525            if (folderUri.Scheme != fileAbsoluteUri.Scheme)
 526            {
 0527                return fileAbsolutePath;
 528            }
 529
 0530            var relativeUri = folderUri.MakeRelativeUri(fileAbsoluteUri);
 0531            string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
 532
 0533            if (fileAbsoluteUri.Scheme.Equals("file", StringComparison.OrdinalIgnoreCase))
 534            {
 0535                relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
 536            }
 537
 0538            return relativePath;
 539        }
 540
 541        /// <inheritdoc />
 542        public Folder GetPlaylistsFolder()
 543        {
 1544            return GetPlaylistsFolder(Guid.Empty);
 545        }
 546
 547        /// <inheritdoc />
 548        public Folder GetPlaylistsFolder(Guid userId)
 549        {
 550            const string TypeName = "PlaylistsFolder";
 551
 1552            return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Na
 1553                _libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetTyp
 554        }
 555
 556        /// <inheritdoc />
 557        public async Task RemovePlaylistsAsync(Guid userId)
 558        {
 559            var playlists = GetUserPlaylists(userId);
 560            foreach (var playlist in playlists)
 561            {
 562                // Update owner if shared
 563                var rankedShares = playlist.Shares.OrderByDescending(x => x.CanEdit).ToList();
 564                if (rankedShares.Count > 0)
 565                {
 566                    playlist.OwnerUserId = rankedShares[0].UserId;
 567                    playlist.Shares = rankedShares.Skip(1).ToArray();
 568                    await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 569                }
 570                else if (!playlist.OpenAccess)
 571                {
 572                    // Remove playlist if not shared
 573                    _libraryManager.DeleteItem(
 574                        playlist,
 575                        new DeleteOptions
 576                        {
 577                            DeleteFileLocation = false,
 578                            DeleteFromExternalProvider = false
 579                        },
 580                        playlist.GetParent(),
 581                        false);
 582                }
 583            }
 584        }
 585
 586        public async Task UpdatePlaylist(PlaylistUpdateRequest request)
 587        {
 588            var playlist = GetPlaylistForUser(request.Id, request.UserId);
 589
 590            if (request.Ids is not null)
 591            {
 592                playlist.LinkedChildren = [];
 593                await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 594
 595                var user = _userManager.GetUserById(request.UserId);
 596                await AddToPlaylistInternal(request.Id, request.Ids, user, new DtoOptions(false)
 597                {
 598                    EnableImages = true
 599                }).ConfigureAwait(false);
 600
 601                playlist = GetPlaylistForUser(request.Id, request.UserId);
 602            }
 603
 604            if (request.Name is not null)
 605            {
 606                playlist.Name = request.Name;
 607            }
 608
 609            if (request.Users is not null)
 610            {
 611                playlist.Shares = request.Users;
 612            }
 613
 614            if (request.Public is not null)
 615            {
 616                playlist.OpenAccess = request.Public.Value;
 617            }
 618
 619            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 620        }
 621
 622        public async Task AddUserToShares(PlaylistUserUpdateRequest request)
 623        {
 624            var userId = request.UserId;
 625            var playlist = GetPlaylistForUser(request.Id, userId);
 626            var shares = playlist.Shares.ToList();
 627            var existingUserShare = shares.FirstOrDefault(s => s.UserId.Equals(userId));
 628            if (existingUserShare is not null)
 629            {
 630                shares.Remove(existingUserShare);
 631            }
 632
 633            shares.Add(new PlaylistUserPermissions(userId, request.CanEdit ?? false));
 634            playlist.Shares = shares;
 635            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 636        }
 637
 638        public async Task RemoveUserFromShares(Guid playlistId, Guid userId, PlaylistUserPermissions share)
 639        {
 640            var playlist = GetPlaylistForUser(playlistId, userId);
 641            var shares = playlist.Shares.ToList();
 642            shares.Remove(share);
 643            playlist.Shares = shares;
 644            await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
 645        }
 646
 647        private async Task UpdatePlaylistInternal(Playlist playlist)
 648        {
 649            await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(f
 650
 651            if (playlist.IsFile)
 652            {
 653                SavePlaylistFile(playlist);
 654            }
 655        }
 656    }
 657}