Exemple #1
0
        public IEnumerator RequestSongByLevelIDCoroutine(string levelId, Action <Song> callback)
        {
            UnityWebRequest wwwId = UnityWebRequest.Get($"{PluginConfig.BeatsaverURL}/api/songs/search/hash/" + levelId);

            wwwId.timeout = 10;

            yield return(wwwId.SendWebRequest());


            if (wwwId.isNetworkError || wwwId.isHttpError)
            {
                Logger.Error(wwwId.error);
            }
            else
            {
#if DEBUG
                Logger.Info("Received response from BeatSaver...");
#endif
                JSONNode node = JSON.Parse(wwwId.downloadHandler.text);

                if (node["songs"].Count == 0)
                {
                    Logger.Error($"Song {levelId} doesn't exist on BeatSaver!");
                    callback?.Invoke(null);
                    yield break;
                }

                Song _tempSong = Song.FromSearchNode(node["songs"][0]);
                callback?.Invoke(_tempSong);
            }
        }
        private void FilterSearch(List <LevelSO> levels)
        {
            // Make sure we can actually search.
            if (this._settings.searchTerms.Count <= 0)
            {
                Logger.Error("Tried to search for a song with no valid search terms...");
                SortSongName(levels);
                return;
            }
            string searchTerm = this._settings.searchTerms[0];

            if (String.IsNullOrEmpty(searchTerm))
            {
                Logger.Error("Empty search term entered.");
                SortSongName(levels);
                return;
            }

            Logger.Info("Filtering song list by search term: {0}", searchTerm);
            _originalSongs.ForEach(x => Logger.Debug($"{x.songName} {x.songSubName} {x.songAuthorName}".ToLower().Contains(searchTerm.ToLower()).ToString()));

            _filteredSongs = levels
                             .Where(x => $"{x.songName} {x.songSubName} {x.songAuthorName}".ToLower().Contains(searchTerm.ToLower()))
                             .ToList();
        }
Exemple #3
0
        private async Task ExtractZipAsync(Song songInfo, Stream zipStream, string customSongsPath)
        {
            try
            {
                Logger.Info("Extracting...");
                _extractingZip = true;
                ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Read);
                await Task.Run(() => archive.ExtractToDirectory(customSongsPath)).ConfigureAwait(false);

                archive.Dispose();
                zipStream.Close();
            }
            catch (Exception e)
            {
                Logger.Exception($"Unable to extract ZIP! Exception:", e);
                songInfo.songQueueState = SongQueueState.Error;
                _extractingZip          = false;
                return;
            }

            songInfo.path = Directory.GetDirectories(customSongsPath).FirstOrDefault();

            if (string.IsNullOrEmpty(songInfo.path))
            {
                songInfo.path = customSongsPath;
            }

            _extractingZip          = false;
            songInfo.songQueueState = SongQueueState.Downloaded;
            _alreadyDownloadedSongs.Add(songInfo);
            Logger.Info($"Extracted {songInfo.songName} {songInfo.songSubName}!");
        }
Exemple #4
0
        private void SortDifficulty(List <BeatmapLevelSO> levels)
        {
            Logger.Info("Sorting song list by difficulty...");

            IEnumerable <BeatmapDifficulty> difficultyIterator       = Enum.GetValues(typeof(BeatmapDifficulty)).Cast <BeatmapDifficulty>();
            Dictionary <string, int>        levelIdToDifficultyValue = new Dictionary <string, int>();

            foreach (var level in levels)
            {
                if (!levelIdToDifficultyValue.ContainsKey(level.levelID))
                {
                    int difficultyValue = 0;

                    // Get the beatmap difficulties
                    var difficulties = level.difficultyBeatmapSets
                                       .Where(x => x.beatmapCharacteristic == this.CurrentBeatmapCharacteristicSO)
                                       .SelectMany(x => x.difficultyBeatmaps);

                    foreach (IDifficultyBeatmap difficultyBeatmap in difficulties)
                    {
                        difficultyValue += _difficultyWeights[difficultyBeatmap.difficulty];
                    }
                    levelIdToDifficultyValue.Add(level.levelID, difficultyValue);
                }
            }

            _sortedSongs = levels
                           .OrderBy(x => levelIdToDifficultyValue[x.levelID])
                           .ThenBy(x => x.songName)
                           .ToList();
            _sortedSongs = levels;
        }
Exemple #5
0
        private List <BeatmapLevelSO> FilterSearch(List <BeatmapLevelSO> levels)
        {
            // Make sure we can actually search.
            if (this._settings.searchTerms.Count <= 0)
            {
                Logger.Error("Tried to search for a song with no valid search terms...");
                SortSongName(levels);
                return(levels);
            }
            string searchTerm = this._settings.searchTerms[0];

            if (String.IsNullOrEmpty(searchTerm))
            {
                Logger.Error("Empty search term entered.");
                SortSongName(levels);
                return(levels);
            }

            Logger.Info("Filtering song list by search term: {0}", searchTerm);

            var terms = searchTerm.Split(' ');

            foreach (var term in terms)
            {
                levels = levels.Intersect(
                    levels
                    .Where(x => $"{x.songName} {x.songSubName} {x.songAuthorName}".ToLower().Contains(term.ToLower()))
                    .ToList(
                        )
                    ).ToList();
            }

            return(levels);
        }
 private void SortNewest(List <LevelSO> levels)
 {
     Logger.Info("Sorting song list as newest.");
     _sortedSongs = levels
                    .OrderBy(x => _weights.ContainsKey(x.levelID) ? _weights[x.levelID] : 0)
                    .ThenByDescending(x => !_levelIdToCustomLevel.ContainsKey(x.levelID) ? (_weights.ContainsKey(x.levelID) ? _weights[x.levelID] : 0) : _cachedLastWriteTimes[x.levelID])
                    .ToList();
 }
 private void SortAuthor(List <LevelSO> levels)
 {
     Logger.Info("Sorting song list by author");
     _sortedSongs = levels
                    .OrderBy(x => x.songAuthorName)
                    .ThenBy(x => x.songName)
                    .ToList();
 }
 private void SortPlayCount(List <LevelSO> levels)
 {
     Logger.Info("Sorting song list by playcount");
     _sortedSongs = levels
                    .OrderByDescending(x => _levelIdToPlayCount[x.levelID])
                    .ThenBy(x => x.songName)
                    .ToList();
 }
        private void SortPerformancePoints(List <LevelSO> levels)
        {
            Logger.Info("Sorting song list by performance points...");

            _sortedSongs = levels
                           .OrderByDescending(x => _levelIdToScoreSaberData.ContainsKey(x.levelID) ? _levelIdToScoreSaberData[x.levelID].maxPp : 0)
                           .ToList();
        }
 private void SortSongName(List <LevelSO> levels)
 {
     Logger.Info("Sorting song list as default (songName)");
     _sortedSongs = levels
                    .OrderBy(x => x.songName)
                    .ThenBy(x => x.songAuthorName)
                    .ToList();
 }
        private void SceneManager_activeSceneChanged(Scene from, Scene to)
        {
            Logger.Info($"Active scene changed from \"{from.name}\" to \"{to.name}\"");

            if (from.name == "EmptyTransition" && to.name.Contains("Menu"))
            {
                OnMenuSceneEnabled();
            }
        }
        public void DownloadAllSongsFromQueue()
        {
            Logger.Info("Downloading all songs from queue...");

            for (int i = 0; i < Math.Min(PluginConfig.MaxSimultaneousDownloads, queuedSongs.Count); i++)
            {
                StartCoroutine(DownloadSong(queuedSongs[i]));
            }
        }
 /// <summary>
 /// For now the editing playlist will be considered the favorites playlist.
 /// Users can edit the settings file themselves.
 /// </summary>
 private void FilterFavorites()
 {
     Logger.Info("Filtering song list as favorites playlist...");
     if (this.CurrentEditingPlaylist != null)
     {
         this.CurrentPlaylist = this.CurrentEditingPlaylist;
     }
     this.FilterPlaylist();
 }
Exemple #14
0
 /// <summary>
 /// For now the editing playlist will be considered the favorites playlist.
 /// Users can edit the settings file themselves.
 /// </summary>
 private List <BeatmapLevelSO> FilterFavorites()
 {
     Logger.Info("Filtering song list as favorites playlist...");
     if (this.CurrentEditingPlaylist != null)
     {
         this.CurrentPlaylist = this.CurrentEditingPlaylist;
     }
     return(this.FilterPlaylist());
 }
        private void SortRandom(List <LevelSO> levels)
        {
            Logger.Info("Sorting song list by random (seed={0})...", this.Settings.randomSongSeed);

            System.Random rnd = new System.Random(this.Settings.randomSongSeed);

            _sortedSongs = levels
                           .OrderBy(x => rnd.Next())
                           .ToList();
        }
 /// <summary>
 ///
 /// </summary>
 /// <param name="matchAll"></param>
 public void MatchSongsForAllPlaylists(bool matchAll = false)
 {
     Logger.Info("Matching songs for all playlists!");
     Task.Run(() =>
     {
         for (int i = 0; i < _playlistsReader.Playlists.Count; i++)
         {
             MatchSongsForPlaylist(_playlistsReader.Playlists[i], matchAll);
         }
     });
 }
 public void AbortDownloads()
 {
     Logger.Info("Cancelling downloads...");
     foreach (Song song in queuedSongs.Where(x => x.songQueueState == SongQueueState.Downloading || x.songQueueState == SongQueueState.Queued))
     {
         song.songQueueState      = SongQueueState.Error;
         song.downloadingProgress = 1f;
     }
     Refresh();
     allSongsDownloaded?.Invoke();
 }
        public IEnumerator DownloadScrappedData(Action <List <ScrappedSong> > callback)
        {
            Logger.Info("Downloading scrapped data...");

            UnityWebRequest www;
            bool            timeout = false;
            float           time    = 0f;
            UnityWebRequestAsyncOperation asyncRequest;

            try
            {
                www = UnityWebRequest.Get(scrappedDataURL);

                asyncRequest = www.SendWebRequest();
            }
            catch (Exception e)
            {
                Logger.Exception("Exception hitting scrappedDataURL", e);
                yield break;
            }

            while (!asyncRequest.isDone)
            {
                yield return(null);

                time += Time.deltaTime;
                if (time >= 5f && asyncRequest.progress == 0f)
                {
                    www.Abort();
                    timeout = true;
                    Logger.Error("Connection timed out!");
                }
            }


            if (www.isNetworkError || www.isHttpError || timeout)
            {
                Logger.Error("Unable to download scrapped data! " + (www.isNetworkError ? $"Network error: {www.error}" : (www.isHttpError ? $"HTTP error: {www.error}" : "Unknown error")));
            }
            else
            {
                Logger.Info("Received response from github.com...");

                Songs = JsonConvert.DeserializeObject <List <ScrappedSong> >(www.downloadHandler.text).OrderByDescending(x => x.Diffs.Count > 0 ? x.Diffs.Max(y => y.Stars) : 0).ToList();

                callback?.Invoke(Songs);
                Logger.Info("Scrapped data downloaded!");
            }
        }
 /// <summary>
 ///
 /// </summary>
 /// <param name="playlist"></param>
 /// <param name="matchAll"></param>
 public static void MatchSongsForPlaylist(Playlist playlist, bool matchAll = false)
 {
     if (!SongLoader.AreSongsLoaded || SongLoader.AreSongsLoading || playlist.Title == "All songs" || playlist.Title == "Your favorite songs")
     {
         return;
     }
     Logger.Info("Started matching songs for playlist \"" + playlist.Title + "\"...");
     if (!playlist.Songs.All(x => x.Level != null) || matchAll)
     {
         Stopwatch execTime = new Stopwatch();
         execTime.Start();
         playlist.Songs.AsParallel().ForAll(x =>
         {
             if (x.Level == null || matchAll)
             {
                 try
                 {
                     if (!string.IsNullOrEmpty(x.LevelId)) //check that we have levelId and if we do, try to match level
                     {
                         x.Level = SongLoader.CustomLevels.FirstOrDefault(y => y.levelID == x.LevelId);
                     }
                     if (x.Level == null && !string.IsNullOrEmpty(x.Hash)) //if level is still null, check that we have hash and if we do, try to match level
                     {
                         x.Level = SongLoader.CustomLevels.FirstOrDefault(y => y.levelID.StartsWith(x.Hash.ToUpper()));
                     }
                     if (x.Level == null && !string.IsNullOrEmpty(x.Key)) //if level is still null, check that we have key and if we do, try to match level
                     {
                         ScrappedSong song = ScrappedData.Songs.FirstOrDefault(z => z.Key == x.Key);
                         if (song != null)
                         {
                             x.Level = SongLoader.CustomLevels.FirstOrDefault(y => y.levelID.StartsWith(song.Hash));
                         }
                         else
                         {
                             x.Level = SongLoader.CustomLevels.FirstOrDefault(y => y.customSongInfo.path.Contains(x.Key));
                         }
                     }
                 }
                 catch (Exception e)
                 {
                     Logger.Warning($"Unable to match song with {(string.IsNullOrEmpty(x.Key) ? " unknown key!" : ("key " + x.Key + " !"))} Exception: {e}");
                 }
             }
         });
         Logger.Info($"Matched all songs for playlist \"{playlist.Title}\"! Time: {execTime.Elapsed.TotalSeconds.ToString("0.00")}s");
         execTime.Reset();
     }
 }
        public void Refresh()
        {
            int removed = queuedSongs.RemoveAll(x => x.songQueueState == SongQueueState.Downloaded || x.songQueueState == SongQueueState.Error);

            Logger.Info($"Removed {removed} songs from queue");

            _queuedSongsTableView.ReloadData();
            _queuedSongsTableView.ScrollToRow(0, true);

            if (queuedSongs.Count(x => x.songQueueState == SongQueueState.Downloading || x.songQueueState == SongQueueState.Queued) == 0)
            {
                Logger.Info("All songs downloaded!");
                allSongsDownloaded?.Invoke();
            }

            if (queuedSongs.Count(x => x.songQueueState == SongQueueState.Downloading) < PluginConfig.MaxSimultaneousDownloads && queuedSongs.Any(x => x.songQueueState == SongQueueState.Queued))
            {
                StartCoroutine(DownloadSong(queuedSongs.First(x => x.songQueueState == SongQueueState.Queued)));
            }
        }
        public IEnumerator DownloadPlaylist(Playlist playlist)
        {
            PlaylistFlowCoordinator.MatchSongsForPlaylist(playlist, true);

            List <PlaylistSong> needToDownload = playlist.Songs.Where(x => x.Level == null).ToList();

            Logger.Info($"Need to download {needToDownload.Count} songs");

            _downloadingPlaylist = true;
            foreach (var item in needToDownload)
            {
                Song beatSaverSong = null;

                if (String.IsNullOrEmpty(playlist.CustomArchiveUrl))
                {
                    Logger.Info("Obtaining hash and url for " + item.Key + ": " + item.SongName);
                    yield return(GetInfoForSong(playlist, item, (Song song) => { beatSaverSong = song; }));
                }
                else
                {
                    string archiveUrl = playlist.CustomArchiveUrl.Replace("[KEY]", item.Key);

                    beatSaverSong = new Song()
                    {
                        songName            = item.SongName,
                        id                  = item.Key,
                        downloadingProgress = 0f,
                        hash                = (item.LevelId == null ? "" : item.LevelId),
                        downloadUrl         = archiveUrl
                    };
                }

                if (beatSaverSong != null && !SongLoader.CustomLevels.Any(x => x.levelID.Substring(0, 32) == beatSaverSong.hash.ToUpper()))
                {
                    _downloadQueueViewController.EnqueueSong(beatSaverSong, true);
                }
            }
            _downloadingPlaylist = false;
        }
        /// <summary>
        /// Wait for score saber related files to download.
        /// </summary>
        /// <returns></returns>
        private IEnumerator WaitForDownload()
        {
            if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile != null)
            {
                Logger.Info("Using cached copy of ScoreSaberData...");
            }
            else
            {
                SongBrowserApplication.MainProgressBar.ShowMessage("Downloading BeatStar data...");

                Logger.Info("Attempting to download: {0}", ScoreSaberDatabaseDownloader.SCRAPED_SCORE_SABER_JSON_URL);
                using (UnityWebRequest www = UnityWebRequest.Get(ScoreSaberDatabaseDownloader.SCRAPED_SCORE_SABER_JSON_URL))
                {
                    // Use 4MB cache, large enough for this file to grow for awhile.
                    www.SetCacheable(new CacheableDownloadHandlerScoreSaberData(www, _buffer));
                    yield return(www.SendWebRequest());

                    Logger.Debug("Returned from web request!...");

                    try
                    {
                        ScoreSaberDatabaseDownloader.ScoreSaberDataFile = (www.downloadHandler as CacheableDownloadHandlerScoreSaberData).ScoreSaberDataFile;
                        Logger.Info("Success downloading ScoreSaber data!");

                        SongBrowserApplication.MainProgressBar.ShowMessage("Success downloading BeatStar data...", 10.0f);
                        onScoreSaberDataDownloaded?.Invoke();
                    }
                    catch (System.InvalidOperationException)
                    {
                        Logger.Error("Failed to download ScoreSaber data file...");
                    }
                    catch (Exception e)
                    {
                        Logger.Exception("Exception trying to download ScoreSaber data file...", e);
                    }
                }
            }
        }
        private void SortDifficulty(List <LevelSO> levels)
        {
            Logger.Info("Sorting song list by difficulty...");

            IEnumerable <BeatmapDifficulty> difficultyIterator       = Enum.GetValues(typeof(BeatmapDifficulty)).Cast <BeatmapDifficulty>();
            Dictionary <string, int>        levelIdToDifficultyValue = new Dictionary <string, int>();

            foreach (var level in levels)
            {
                if (!levelIdToDifficultyValue.ContainsKey(level.levelID))
                {
                    // Skip folders
                    if (level.levelID.StartsWith("Folder_"))
                    {
                        levelIdToDifficultyValue.Add(level.levelID, 0);
                    }
                    else
                    {
                        int difficultyValue = 0;
                        foreach (BeatmapDifficulty difficulty in difficultyIterator)
                        {
                            IDifficultyBeatmap beatmap = level.GetDifficultyBeatmap(difficulty);
                            if (beatmap != null)
                            {
                                difficultyValue += _difficultyWeights[difficulty];
                                break;
                            }
                        }
                        levelIdToDifficultyValue.Add(level.levelID, difficultyValue);
                    }
                }
            }

            _sortedSongs = levels
                           .OrderBy(x => levelIdToDifficultyValue[x.levelID])
                           .ThenBy(x => x.songName)
                           .ToList();
        }
Exemple #24
0
        public IEnumerator RequestSongByKeyCoroutine(string key, Action <Song> callback)
        {
            UnityWebRequest wwwId = UnityWebRequest.Get($"{PluginConfig.BeatsaverURL}/api/songs/detail/" + key);

            wwwId.timeout = 10;

            yield return(wwwId.SendWebRequest());


            if (wwwId.isNetworkError || wwwId.isHttpError)
            {
                Logger.Error(wwwId.error);
            }
            else
            {
#if DEBUG
                Logger.Info("Received response from BeatSaver...");
#endif
                JSONNode node = JSON.Parse(wwwId.downloadHandler.text);

                Song _tempSong = new Song(node["song"]);
                callback?.Invoke(_tempSong);
            }
        }
Exemple #25
0
        public bool DeleteSong(Song song)
        {
            bool   zippedSong = false;
            string path       = "";

            CustomLevel level = SongLoader.CustomLevels.FirstOrDefault(x => x.levelID.StartsWith(song.hash));

            if (string.IsNullOrEmpty(song.path))
            {
                if (level != null)
                {
                    path = level.customSongInfo.path;
                }
            }
            else
            {
                path = song.path;
            }

            if (string.IsNullOrEmpty(path))
            {
                return(false);
            }
            if (!Directory.Exists(path))
            {
                return(false);
            }

            if (path.Contains("/.cache/"))
            {
                zippedSong = true;
            }

            if (zippedSong)
            {
                Logger.Info("Deleting \"" + path.Substring(path.LastIndexOf('/')) + "\"...");
                Directory.Delete(path, true);

                string songHash = Directory.GetParent(path).Name;

                try
                {
                    if (Directory.GetFileSystemEntries(path.Substring(0, path.LastIndexOf('/'))).Length == 0)
                    {
                        Logger.Info("Deleting empty folder \"" + path.Substring(0, path.LastIndexOf('/')) + "\"...");
                        Directory.Delete(path.Substring(0, path.LastIndexOf('/')), false);
                    }
                }
                catch
                {
                    Logger.Warning("Can't find or delete empty folder!");
                }

                string docPath = Application.dataPath;
                docPath = docPath.Substring(0, docPath.Length - 5);
                docPath = docPath.Substring(0, docPath.LastIndexOf("/"));
                string customSongsPath = docPath + "/CustomSongs/";

                string hash = "";

                foreach (string file in Directory.GetFiles(customSongsPath, "*.zip"))
                {
                    if (CreateMD5FromFile(file, out hash))
                    {
                        if (hash == songHash)
                        {
                            File.Delete(file);
                            break;
                        }
                    }
                }
            }
            else
            {
                Logger.Info("Deleting \"" + path.Substring(path.LastIndexOf('/')) + "\"...");
                Directory.Delete(path, true);

                try
                {
                    if (Directory.GetFileSystemEntries(path.Substring(0, path.LastIndexOf('/'))).Length == 0)
                    {
                        Logger.Info("Deleting empty folder \"" + path.Substring(0, path.LastIndexOf('/')) + "\"...");
                        Directory.Delete(path.Substring(0, path.LastIndexOf('/')), false);
                    }
                }
                catch
                {
                    Logger.Warning("Unable to delete empty folder!");
                }
            }

            if (level != null)
            {
                SongLoader.Instance.RemoveSongWithLevelID(level.levelID);
            }

            Logger.Info($"{_alreadyDownloadedSongs.RemoveAll(x => x.Compare(song))} song removed");
            return(true);
        }
Exemple #26
0
        public IEnumerator DownloadSongCoroutine(Song songInfo)
        {
            songInfo.songQueueState = SongQueueState.Downloading;

            UnityWebRequest www;
            bool            timeout = false;
            float           time    = 0f;
            UnityWebRequestAsyncOperation asyncRequest;

            try
            {
                www = UnityWebRequest.Get(songInfo.downloadUrl);

                asyncRequest = www.SendWebRequest();
            }
            catch (Exception e)
            {
                Logger.Exception("DownloadSongCoroutine Exception", e);
                songInfo.songQueueState      = SongQueueState.Error;
                songInfo.downloadingProgress = 1f;

                yield break;
            }

            while ((!asyncRequest.isDone || songInfo.downloadingProgress != 1f) && songInfo.songQueueState != SongQueueState.Error)
            {
                yield return(null);

                time += Time.deltaTime;

                if ((time >= 5f && asyncRequest.progress == 0f) || songInfo.songQueueState == SongQueueState.Error)
                {
                    www.Abort();
                    timeout = true;
                    Logger.Error("Connection timed out!");
                }

                songInfo.downloadingProgress = asyncRequest.progress;
            }


            if (www.isNetworkError || www.isHttpError || timeout || songInfo.songQueueState == SongQueueState.Error)
            {
                songInfo.songQueueState = SongQueueState.Error;
                Logger.Error("Unable to download song! " + (www.isNetworkError ? $"Network error: {www.error}" : (www.isHttpError ? $"HTTP error: {www.error}" : "Unknown error")));
            }
            else
            {
                Logger.Info("Received response from BeatSaver.com...");

                //string zipPath = "";
                string docPath         = "";
                string customSongsPath = "";

                byte[] data = www.downloadHandler.data;

                Stream zipStream = null;

                try
                {
                    docPath         = Application.dataPath;
                    docPath         = docPath.Substring(0, docPath.Length - 5);
                    docPath         = docPath.Substring(0, docPath.LastIndexOf("/"));
                    customSongsPath = docPath + "/CustomSongs/" + songInfo.id + "/";
                    if (!Directory.Exists(customSongsPath))
                    {
                        Directory.CreateDirectory(customSongsPath);
                    }
                    zipStream = new MemoryStream(data);
                    Logger.Info("Downloaded zip!");
                }
                catch (Exception e)
                {
                    Logger.Exception("DownloadSongCoroutine exception:", e);
                    songInfo.songQueueState = SongQueueState.Error;
                    yield break;
                }

                while (_extractingZip)
                {
                    yield return(new WaitForSecondsRealtime(0.25f));
                }

                yield return(new WaitWhile(() => _extractingZip)); //because extracting several songs at once sometimes hangs the game

                Task extract = ExtractZipAsync(songInfo, zipStream, customSongsPath);
                yield return(new WaitWhile(() => !extract.IsCompleted));

                songDownloaded?.Invoke(songInfo);
            }
        }
 private void SortOriginal(List <LevelSO> levels)
 {
     Logger.Info("Sorting song list as original");
     _sortedSongs = levels;
 }
 /// <summary>
 /// Init this model.
 /// </summary>
 /// <param name="songSelectionMasterView"></param>
 /// <param name="songListViewController"></param>
 public void Init()
 {
     _settings = SongBrowserSettings.Load();
     Logger.Info("Settings loaded, sorting mode is: {0}", _settings.sortMode);
 }
        /// <summary>
        /// Get the song cache from the game.
        /// </summary>
        public void UpdateSongLists(BeatmapCharacteristicSO gameplayMode)
        {
            // give up
            if (gameplayMode == null)
            {
                Logger.Debug("Always null first time if user waits for SongLoader event, which they should...");
                return;
            }

            Stopwatch timer = new Stopwatch();

            timer.Start();

            // Get the level collection from song loader
            LevelCollectionSO levelCollections = Resources.FindObjectsOfTypeAll <LevelCollectionSO>().FirstOrDefault();

            // Stash everything we need
            _originalSongs = levelCollections.GetLevelsWithBeatmapCharacteristic(gameplayMode).ToList();

            Logger.Debug("Got {0} songs from level collections...", _originalSongs.Count);
            //_originalSongs.ForEach(x => Logger.Debug("{0} by {1} = {2}", x.name, x.levelAuthorName, x.levelID));

            _sortedSongs = _originalSongs;
            CurrentBeatmapCharacteristicSO = gameplayMode;

            // Calculate some information about the custom song dir
            String customSongsPath                   = Path.Combine(Environment.CurrentDirectory, CUSTOM_SONGS_DIR);
            String revSlashCustomSongPath            = customSongsPath.Replace('\\', '/');
            double currentCustomSongDirLastWriteTIme = (File.GetLastWriteTimeUtc(customSongsPath) - EPOCH).TotalMilliseconds;
            bool   customSongDirChanged              = false;

            if (_customSongDirLastWriteTime != currentCustomSongDirLastWriteTIme)
            {
                customSongDirChanged        = true;
                _customSongDirLastWriteTime = currentCustomSongDirLastWriteTIme;
            }

            // This operation scales well
            if (!Directory.Exists(customSongsPath))
            {
                Logger.Error("CustomSong directory is missing...");
                return;
            }
            IEnumerable <string> directories = Directory.EnumerateDirectories(customSongsPath, "*.*", SearchOption.AllDirectories);

            // Get LastWriteTimes
            Stopwatch lastWriteTimer = new Stopwatch();

            lastWriteTimer.Start();
            foreach (var level in SongLoader.CustomLevels)
            {
                // If we already know this levelID, don't both updating it.
                // SongLoader should filter duplicates but in case of failure we don't want to crash

                if (!_cachedLastWriteTimes.ContainsKey(level.levelID) || customSongDirChanged)
                {
                    _cachedLastWriteTimes[level.levelID] = (File.GetLastWriteTimeUtc(level.customSongInfo.path) - EPOCH).TotalMilliseconds;
                }

                if (!_levelIdToCustomLevel.ContainsKey(level.levelID))
                {
                    _levelIdToCustomLevel.Add(level.levelID, level);
                }

                if (!_levelIdToSongVersion.ContainsKey(level.levelID))
                {
                    DirectoryInfo info = new DirectoryInfo(level.customSongInfo.path);
                    string        currentDirectoryName = info.Name;

                    String version = level.customSongInfo.path.Replace(revSlashCustomSongPath, "").Replace(currentDirectoryName, "").Replace("/", "");
                    if (!String.IsNullOrEmpty(version))
                    {
                        //Logger.Debug("MATCH");
                        _levelIdToSongVersion.Add(level.levelID, version);
                        _keyToSong.Add(version, level);
                    }
                }
            }

            lastWriteTimer.Stop();
            Logger.Info("Determining song download time and determining mappings took {0}ms", lastWriteTimer.ElapsedMilliseconds);

            // Update song Infos, directory tree, and sort
            this.UpdateScoreSaberDataMapping();
            this.UpdatePlayCounts();
            this.UpdateDirectoryTree(customSongsPath);

            // Check if we need to upgrade settings file favorites
            try
            {
                this.Settings.ConvertFavoritesToPlaylist(_levelIdToCustomLevel, _levelIdToSongVersion);
            }
            catch (Exception e)
            {
                Logger.Exception("FAILED TO CONVERT FAVORITES TO PLAYLIST!", e);
            }

            // load the current editing playlist or make one
            if (!String.IsNullOrEmpty(this.Settings.currentEditingPlaylistFile))
            {
                CurrentEditingPlaylist = PlaylistsReader.ParsePlaylist(this.Settings.currentEditingPlaylistFile);
            }

            if (CurrentEditingPlaylist == null)
            {
                CurrentEditingPlaylist = new Playlist
                {
                    Title  = "Song Browser Favorites",
                    Author = "SongBrowserPlugin",
                    Path   = this.Settings.currentEditingPlaylistFile,
                    Image  = Base64Sprites.PlaylistIconB64,
                    Songs  = new List <PlaylistSong>(),
                };
            }

            CurrentEditingPlaylistLevelIds = new HashSet <string>();
            foreach (PlaylistSong ps in CurrentEditingPlaylist.Songs)
            {
                CurrentEditingPlaylistLevelIds.Add(ps.LevelId);
            }

            // Actually sort and filter
            this.ProcessSongList();

            // Signal complete
            if (SongLoader.CustomLevels.Count > 0)
            {
                didFinishProcessingSongs?.Invoke(SongLoader.CustomLevels);
            }

            timer.Stop();

            Logger.Info("Updating songs infos took {0}ms", timer.ElapsedMilliseconds);
            Logger.Debug("Song Browser knows about {0} songs from SongLoader...", _originalSongs.Count);
        }
        /// <summary>
        /// Sort the song list based on the settings.
        /// </summary>
        public void ProcessSongList()
        {
            Logger.Trace("ProcessSongList()");

            // This has come in handy many times for debugging issues with Newest.

            /*foreach (LevelSO level in _originalSongs)
             * {
             *  if (_levelIdToCustomLevel.ContainsKey(level.levelID))
             *  {
             *      Logger.Debug("HAS KEY {0}: {1}", _levelIdToCustomLevel[level.levelID].customSongInfo.path, level.levelID);
             *  }
             *  else
             *  {
             *      Logger.Debug("Missing KEY: {0}", level.levelID);
             *  }
             * }*/

            if (_directoryStack.Count <= 0)
            {
                Logger.Debug("Cannot process songs yet, songs infos have not been processed...");
                return;
            }

            // Playlist filter will load the original songs.
            if (this._settings.filterMode == SongFilterMode.Playlist && this.CurrentPlaylist != null)
            {
                _originalSongs = null;
            }
            else
            {
                Logger.Debug("Showing songs for directory: {0}", _directoryStack.Peek().Key);
                _originalSongs = _directoryStack.Peek().Levels;
            }

            // filter
            Logger.Debug("Starting filtering songs...");
            Stopwatch stopwatch = Stopwatch.StartNew();

            switch (_settings.filterMode)
            {
            case SongFilterMode.Favorites:
                FilterFavorites();
                break;

            case SongFilterMode.Search:
                FilterSearch(_originalSongs);
                break;

            case SongFilterMode.Playlist:
                FilterPlaylist();
                break;

            case SongFilterMode.None:
            default:
                Logger.Info("No song filter selected...");
                _filteredSongs = _originalSongs;
                break;
            }

            stopwatch.Stop();
            Logger.Info("Filtering songs took {0}ms", stopwatch.ElapsedMilliseconds);

            // sort
            Logger.Debug("Starting to sort songs...");
            stopwatch = Stopwatch.StartNew();

            switch (_settings.sortMode)
            {
            case SongSortMode.Original:
                SortOriginal(_filteredSongs);
                break;

            case SongSortMode.Newest:
                SortNewest(_filteredSongs);
                break;

            case SongSortMode.Author:
                SortAuthor(_filteredSongs);
                break;

            case SongSortMode.PlayCount:
                SortPlayCount(_filteredSongs);
                break;

            case SongSortMode.PP:
                SortPerformancePoints(_filteredSongs);
                break;

            case SongSortMode.Difficulty:
                SortDifficulty(_filteredSongs);
                break;

            case SongSortMode.Random:
                SortRandom(_filteredSongs);
                break;

            case SongSortMode.Default:
            default:
                SortSongName(_filteredSongs);
                break;
            }

            if (this.Settings.invertSortResults && _settings.sortMode != SongSortMode.Random)
            {
                _sortedSongs.Reverse();
            }

            stopwatch.Stop();
            Logger.Info("Sorting songs took {0}ms", stopwatch.ElapsedMilliseconds);

            //_sortedSongs.ForEach(x => Logger.Debug(x.levelID));
        }