internal override void PerformOp(OpContext context) { if (string.IsNullOrEmpty(Playlist.PlaylistID)) { throw new Exception("PlaylistID must be provided."); } if (string.IsNullOrEmpty(Playlist.PlaylistName)) { throw new Exception($"PlaylistName must be provided for ID {Playlist.PlaylistName}"); } var songsAssetFile = context.Engine.GetSongsAssetsFile(); BeatmapLevelPackObject levelPack = null; if (context.Cache.PlaylistCache.ContainsKey(Playlist.PlaylistID)) { Log.LogMsg($"Playlist {Playlist.PlaylistID} will be updated"); levelPack = context.Cache.PlaylistCache[Playlist.PlaylistID].Playlist; levelPack.PackName = Playlist.PlaylistName; Playlist.LevelPackObject = levelPack; OpCommon.UpdateCoverImage(Playlist, context, songsAssetFile); } else { levelPack = OpCommon.CreatePlaylist(context, Playlist, songsAssetFile); } }
internal override void PerformOp(OpContext context) { if (string.IsNullOrEmpty(SongID)) { throw new InvalidOperationException("SongID must be provided."); } if (!context.Cache.SongCache.ContainsKey(SongID)) { throw new InvalidOperationException("SongID does not exist."); } OpCommon.DeleteSong(context, SongID); }
internal override void PerformOp(OpContext context) { if (string.IsNullOrEmpty(PlaylistID)) { throw new InvalidOperationException("Playlist ID must be provided."); } if (!context.Cache.PlaylistCache.ContainsKey(PlaylistID)) { throw new InvalidOperationException("Playlist ID does not exist."); } if (PlaylistID == "CustomSongs") { throw new InvalidOperationException("Don't delete CustomSongs playlist, it's needed for things."); } OpCommon.DeletePlaylist(context, PlaylistID, DeleteSongsOnPlaylist); }
internal override void PerformOp(OpContext context) { Comparer <SongAndPlaylist> comparer; Func <SongAndPlaylist, string> nameGetter; switch (SortMode) { case PlaylistSortMode.Name: nameGetter = (s) => { if (s.Song.SongName?.Length < 1) { return(""); } return(s.Song.SongName.Substring(0, 1).ToUpper()); }; comparer = Comparer <SongAndPlaylist> .Create((s1, s2) => { try { return(s1.Song.SongName.ToUpper().CompareTo(s2.Song.SongName.ToUpper())); } catch { return(-1); } }); break; case PlaylistSortMode.MaxDifficulty: nameGetter = (s) => { var max = s.Song?.DifficultyBeatmapSets?.SelectMany(x => x?.DifficultyBeatmaps)?.Max(x => x?.Difficulty); if (max == null) { return(Difficulty.Easy.ToString()); } return(max.ToString()); }; comparer = Comparer <SongAndPlaylist> .Create((s1, s2) => { try { return(s1.Song.DifficultyBeatmapSets.SelectMany(x => x.DifficultyBeatmaps).Max(x => x.Difficulty) .CompareTo(s2.Song.DifficultyBeatmapSets.SelectMany(x => x.DifficultyBeatmaps).Max(x => x.Difficulty))); } catch { return(-1); } }); break; case PlaylistSortMode.LevelAuthor: nameGetter = (s) => { return(s.Song?.LevelAuthorName?.ToUpper() ?? ""); }; comparer = Comparer <SongAndPlaylist> .Create((s1, s2) => { try { return(s1.Song.LevelAuthorName.ToUpper().CompareTo(s2.Song.LevelAuthorName.ToUpper())); } catch { return(-1); } }); break; default: throw new NotImplementedException("Unhandled playlist sort mode."); } var songList = context.Cache.SongCache.Values.ToArray(); Array.Sort(songList, comparer); var songsAssetFile = context.Engine.GetSongsAssetsFile(); PlaylistAndSongs currentPlaylist = null; int plCtr = 0; string previousSongName = ""; for (int i = 0; i < songList.Length; i++) { var song = songList[i]; if (BSConst.KnownLevelIDs.Contains(song.Song.LevelID) || BSConst.KnownLevelPackIDs.Contains(song.Playlist.PackID)) { continue; } var curSongName = nameGetter(song); var packID = $"Auto_{curSongName}"; if (currentPlaylist == null || currentPlaylist.Playlist.PackID != packID) { bool newPlaylist = true; if (currentPlaylist != null && SortMode == PlaylistSortMode.Name) { if (plCtr < MaxPerNamePlaylist) { newPlaylist = false; } else { currentPlaylist.Playlist.PackName += " - " + previousSongName; } } if (newPlaylist) { if (!context.Cache.PlaylistCache.ContainsKey(packID)) { OpCommon.CreatePlaylist(context, new Models.BeatSaberPlaylist() { PlaylistID = packID, PlaylistName = curSongName }, songsAssetFile); currentPlaylist = context.Cache.PlaylistCache[packID]; } else { currentPlaylist = context.Cache.PlaylistCache[packID]; currentPlaylist.Playlist.PackName = curSongName; } plCtr = 0; } } //update assets var oldPl = context.Cache.PlaylistCache[song.Playlist.PackID]; var oldPtr = oldPl.Playlist.BeatmapLevelCollection.Object.BeatmapLevels.Where(x => x.Object.LevelID == song.Song.LevelID).First(); oldPtr.Dispose(); oldPl.Playlist.BeatmapLevelCollection.Object.BeatmapLevels.Remove(oldPtr); currentPlaylist.Playlist.BeatmapLevelCollection.Object.BeatmapLevels.Add(song.Song.PtrFrom(currentPlaylist.Playlist.BeatmapLevelCollection.Object)); //update cache oldPl.Songs.Remove(song.Song.LevelID); song.Playlist = currentPlaylist.Playlist; currentPlaylist.Songs.Add(song.Song.LevelID, new OrderedSong() { Song = song.Song, Order = plCtr }); plCtr++; previousSongName = curSongName; if (SortMode == PlaylistSortMode.Name && i == songList.Length - 1) { currentPlaylist.Playlist.PackName += " - " + curSongName; } } if (RemoveEmptyPlaylists) { foreach (var pl in context.Cache.PlaylistCache.ToList()) { //leave CustomSongs alone if (pl.Value.Songs.Count < 1 && pl.Key != "CustomSongs") { OpCommon.DeletePlaylist(context, pl.Key, false); } } } }
public static void DeletePlaylist(OpContext context, string playlistID, bool deleteSongsOnPlaylist) { var playlist = context.Cache.PlaylistCache[playlistID]; if (deleteSongsOnPlaylist) { try { foreach (var song in playlist.Songs.ToList()) { try { OpCommon.DeleteSong(context, song.Key); } catch (Exception ex) { Log.LogErr($"Exception trying to delete song id {song.Key} while deleting playlist id {playlist.Playlist.PackID}, will be unlinked in cache. This may leave unused data in the assets.", ex); try { context.Cache.SongCache.Remove(song.Value.Song.LevelID); playlist.Songs.Remove(song.Value.Song.LevelID); } catch (Exception ex2) { Log.LogErr($"Exception cleaning up cache for song id {song.Key} in playlist if {playlist.Playlist.PackID} while recovering from a failed delete.", ex2); } } } } catch (Exception ex) { //really this shouldn't ever get hit anymore. Probably can remove it. Log.LogErr($"Deleting songs on playlist ID {playlist?.Playlist?.PackID} failed! Attempting to recover by removing links to songs, although this may leave extra stuff in assets!"); try { var bmCol = playlist?.Playlist?.BeatmapLevelCollection?.Object?.BeatmapLevels?.ToList(); if (bmCol != null) { bmCol.ForEach(x => { playlist.Playlist.BeatmapLevelCollection.Object.BeatmapLevels.Remove(x); x.Dispose(); }); } } catch (Exception ex2) { Log.LogErr($"Failed to recover by removing song pointers while deleting playlist {playlist?.Playlist?.PackID}! This will definitely leave stuff in assets.", ex2); } } playlist.Songs.Clear(); } //this should be done in song delete already //playlist.Playlist.BeatmapLevelCollection.Object.BeatmapLevels.ForEach(x => { x.Target.ParentFile.DeleteObject(x.Object); x.Dispose(); }); var mlp = context.Engine.GetMainLevelPack(); var aop = context.Engine.GetAlwaysOwnedModel(); var mlpptr = mlp.BeatmapLevelPacks.FirstOrDefault(x => x.Object.PackID == playlist.Playlist.PackID); var aopptr = aop.AlwaysOwnedPacks.FirstOrDefault(x => x.Object.PackID == playlist.Playlist.PackID); if (mlpptr != null) { mlp.BeatmapLevelPacks.Remove(mlpptr); mlpptr.Dispose(); } else { Log.LogErr($"The playlist id {playlist.Playlist.PackID} didn't exist in the main level packs"); } if (aopptr != null) { aop.AlwaysOwnedPacks.Remove(aopptr); aopptr.Dispose(); } else { Log.LogErr($"The playlist id {playlist.Playlist.PackID} didn't exist in the always owned level packs"); } //don't delete built in packs assets, just unlink them if (!BSConst.KnownLevelPackIDs.Contains(playlist.Playlist.PackID)) { var plParent = playlist.Playlist.ObjectInfo.ParentFile; plParent.DeleteObject(playlist.Playlist.CoverImage.Object.RenderData.Texture.Object); playlist.Playlist.CoverImage.Object.RenderData.Texture.Dispose(); plParent.DeleteObject(playlist.Playlist.CoverImage.Object); plParent.DeleteObject(playlist.Playlist); playlist.Playlist.CoverImage.Dispose(); } context.Cache.PlaylistCache.Remove(playlistID); }
internal override void PerformOp(OpContext context) { if (string.IsNullOrEmpty(Song.SongID)) { throw new InvalidOperationException("SongID must be set on the song!"); } if (string.IsNullOrEmpty(Song.CustomSongPath)) { throw new InvalidOperationException("CustomSongPath must be set on the song!"); } if (!context.Cache.PlaylistCache.ContainsKey(PlaylistID)) { throw new KeyNotFoundException($"PlaylistID {PlaylistID} not found in the cache!"); } bool exists = context.Cache.SongCache.ContainsKey(Song.SongID); if (exists && !OverwriteIfExists) { throw new AddSongException(AddSongFailType.SongExists, $"SongID {Song.SongID} already exists!"); } if (exists && OverwriteIfExists) { OpCommon.DeleteSong(context, Song.SongID); } if (context.Cache.SongCache.ContainsKey(Song.SongID)) { throw new AddSongException(AddSongFailType.SongExists, $"SongID {Song.SongID} already exists, even though it should have been deleted to be replaced!"); } BeatmapLevelDataObject level = null; try { var songsAssetFile = context.Engine.GetSongsAssetsFile(); CustomLevelLoader loader = new CustomLevelLoader(songsAssetFile, context.Config); var deser = loader.DeserializeFromJson(Song.CustomSongPath, Song.SongID); level = loader.LoadSongToAsset(deser, Song.CustomSongPath, true); } catch (Exception ex) { throw new Exception($"Exception loading custom song folder '{Song.CustomSongPath}' for SongID {Song.SongID}", ex); } if (level == null) { throw new AddSongException(AddSongFailType.InvalidFormat, $"Song at folder '{Song.CustomSongPath}' for SongID {Song.SongID} failed to load"); } Song.LevelData = level; Song.LevelAuthorName = level.LevelAuthorName; Song.SongAuthorName = level.SongAuthorName; Song.SongName = level.SongName; Song.SongSubName = level.SongSubName; var playlist = context.Cache.PlaylistCache[PlaylistID]; playlist.Playlist.BeatmapLevelCollection.Object.BeatmapLevels.Add(Song.LevelData.PtrFrom(playlist.Playlist.BeatmapLevelCollection.Object)); playlist.Songs.Add(Song.SongID, new OrderedSong() { Song = Song.LevelData, Order = playlist.Songs.Count }); context.Cache.SongCache.Add(Song.SongID, new SongAndPlaylist() { Playlist = playlist.Playlist, Song = Song.LevelData }); var qfos = context.Engine.QueuedFileOperations.Where(x => x.Tag == Song.SongID && x.Type == QueuedFileOperationType.DeleteFolder || x.Type == QueuedFileOperationType.DeleteFile).ToList(); foreach (var q in qfos) { context.Engine.QueuedFileOperations.Remove(q); } }