Ver código fonte

Merge pull request #87 from halsafar/devel

Halsafar 5 anos atrás
pai
commit
e4fc955f77

+ 0 - 204
SongBrowserPlugin/DataAccess/Network/CacheableDownloadHandler.cs

@@ -1,204 +0,0 @@
-using UnityEngine.Networking;
-using System.IO;
-using System.Security.Cryptography;
-using System.Text;
-using System;
-using UnityEngine;
-
-using Logger = SongBrowser.Logging.Logger;
-
-// Modified Version of:
-// https://github.com/mob-sakai/AssetSystem/blob/master/Assets/Mobcast/Coffee/AssetSystem/CacheableDownloadHandler.cs
-// MIT-LICENSE - https://github.com/mob-sakai/AssetSystem/blob/master/LICENSE
-// Modified to use custom logging only.
-namespace Mobcast.Coffee.AssetSystem
-{
-
-	public static class UnityWebRequestCachingExtensions
-	{
-		/// <summary>
-		/// Set UnityWebRequest to be cacheable(Etag).
-		/// </summary>
-		public static void SetCacheable(this UnityWebRequest www, CacheableDownloadHandler handler)
-		{
-			var etag = CacheableDownloadHandler.GetCacheEtag(www.url);
-			if (etag != null)
-				www.SetRequestHeader("If-None-Match", etag);
-			www.downloadHandler = handler;
-		}
-	}
-
-	/// <summary>
-	/// Cacheable download handler texture.
-	/// </summary>
-	public class CacheableDownloadHandlerTexture : CacheableDownloadHandler
-	{
-		Texture2D m_Texture;
-
-		public CacheableDownloadHandlerTexture(UnityWebRequest www, byte[] preallocateBuffer)
-			: base(www, preallocateBuffer)
-		{
-		}
-
-		/// <summary>
-		/// Returns the downloaded Texture, or null.
-		/// </summary>
-		public Texture2D Texture
-		{
-			get
-			{
-				if (m_Texture == null)
-				{
-					m_Texture = new Texture2D(1, 1);
-					m_Texture.LoadImage(GetData(), true);
-				}
-				return m_Texture;
-			}
-		}
-	}
-
-	/// <summary>
-	/// Cacheable download handler.
-	/// </summary>
-	public abstract class CacheableDownloadHandler : DownloadHandlerScript
-	{
-        const string kLog = "[WebRequestCaching] ";
-		const string kDataSufix = "_d";
-		const string kEtagSufix = "_e";
-
-		static string s_WebCachePath; 
-		static SHA1CryptoServiceProvider s_SHA1 = new SHA1CryptoServiceProvider();
-
-		/// <summary>
-		/// Is the download already finished?
-		/// </summary>
-		public new bool isDone { get; private set; }
-
-
-		UnityWebRequest m_WebRequest;
-		MemoryStream m_Stream;
-		protected byte[] m_Buffer;
-
-		internal CacheableDownloadHandler(UnityWebRequest www, byte[] preallocateBuffer)
-			: base(preallocateBuffer)
-		{
-			this.m_WebRequest = www;
-			m_Stream = new MemoryStream(preallocateBuffer.Length);
-		}
-
-		/// <summary>
-		/// Get path for web-caching.
-		/// </summary>
-		public static string GetCachePath(string url)
-		{
-			if (s_WebCachePath == null)
-			{
-				s_WebCachePath = Application.temporaryCachePath + "/WebCache/";
-                Logger.Debug("{0}WebCachePath : {1}", kLog, s_WebCachePath);
-
-			}
-
-			if (!Directory.Exists(s_WebCachePath))
-				Directory.CreateDirectory(s_WebCachePath);
-
-			return s_WebCachePath + Convert.ToBase64String(s_SHA1.ComputeHash(UTF8Encoding.Default.GetBytes(url))).Replace('/', '_');
-		}
-
-		/// <summary>
-		/// Get cached Etag for url.
-		/// </summary>
-		public static string GetCacheEtag(string url)
-		{
-			var path = GetCachePath(url);
-			var infoPath = path + kEtagSufix;
-			var dataPath = path + kDataSufix;
-			return File.Exists(infoPath) && File.Exists(dataPath)
-					? File.ReadAllText(infoPath)
-					: null;
-		}
-
-		/// <summary>
-		/// Load cached data for url.
-		/// </summary>
-		public static byte[] LoadCache(string url)
-		{
-			return File.ReadAllBytes(GetCachePath(url) + kDataSufix);
-		}
-
-		/// <summary>
-		/// Save cache data for url.
-		/// </summary>
-		public static void SaveCache(string url, string etag, byte[] datas)
-		{
-			var path = GetCachePath(url);
-			File.WriteAllText(path + kEtagSufix, etag);
-			File.WriteAllBytes(path + kDataSufix, datas);
-		}
-
-		/// <summary>
-		/// Callback, invoked when the data property is accessed.
-		/// </summary>
-		protected override byte[] GetData()
-		{
-			if (!isDone)
-			{
-				Logger.Error("{0}Downloading is not completed : {1}", kLog, m_WebRequest.url);
-				throw new InvalidOperationException("Downloading is not completed. " + m_WebRequest.url);
-			}
-			else if (m_Buffer == null)
-			{
-				// Etag cache hit!
-				if (m_WebRequest.responseCode == 304)
-				{
-					Logger.Debug("<color=green>{0}Etag cache hit : {1}</color>", kLog, m_WebRequest.url);
-					m_Buffer = LoadCache(m_WebRequest.url);
-				}
-				// Download is completed successfully.
-				else if (m_WebRequest.responseCode == 200)
-				{
-					Logger.Debug("<color=green>{0}Download is completed successfully : {1}</color>", kLog, m_WebRequest.url);
-					m_Buffer = m_Stream.GetBuffer();
-					SaveCache(m_WebRequest.url, m_WebRequest.GetResponseHeader("Etag"), m_Buffer);
-				}
-			}
-
-			if (m_Stream != null)
-			{
-				m_Stream.Dispose();
-				m_Stream = null;
-			}
-			return m_Buffer;
-		}
-
-		/// <summary>
-		/// Callback, invoked as data is received from the remote server.
-		/// </summary>
-		protected override bool ReceiveData(byte[] data, int dataLength)
-		{
-			m_Stream.Write(data, 0, dataLength);
-			return true;
-		}
-
-		/// <summary>
-		/// Callback, invoked when all data has been received from the remote server.
-		/// </summary>
-		protected override void CompleteContent()
-		{
-			base.CompleteContent();
-			isDone = true;
-		}
-
-		/// <summary>
-		/// Signals that this [DownloadHandler] is no longer being used, and should clean up any resources it is using.
-		/// </summary>
-		public new void Dispose()
-		{
-			base.Dispose();
-			if (m_Stream != null)
-			{
-				m_Stream.Dispose();
-				m_Stream = null;
-			}
-		}
-	}
-}

+ 0 - 33
SongBrowserPlugin/DataAccess/Network/CacheableDownloadHandlerScoreSaberData.cs

@@ -1,33 +0,0 @@
-using Mobcast.Coffee.AssetSystem;
-using UnityEngine.Networking;
-
-namespace SongBrowser.DataAccess.Network
-{
-    /// <summary>
-	/// Cacheable download handler for score saber tsv file.
-	/// </summary>
-	public class CacheableDownloadHandlerScoreSaberData : CacheableDownloadHandler
-    {
-        ScoreSaberDataFile _scoreSaberDataFile;
-
-        public CacheableDownloadHandlerScoreSaberData(UnityWebRequest www, byte[] preallocateBuffer)
-            : base(www, preallocateBuffer)
-        {
-        }
-
-        /// <summary>
-        /// Returns the downloaded score saber data file, or null.
-        /// </summary>
-        public ScoreSaberDataFile ScoreSaberDataFile
-        {
-            get
-            {
-                if (_scoreSaberDataFile == null)
-                {
-                    _scoreSaberDataFile = new ScoreSaberDataFile(GetData());
-                }
-                return _scoreSaberDataFile;
-            }
-        }
-    }
-}

+ 51 - 27
SongBrowserPlugin/DataAccess/Playlist.cs

@@ -141,9 +141,20 @@ namespace SongBrowser.DataAccess
 
         public static void MatchSongsForPlaylist(Playlist playlist, bool matchAll = false)
         {
-            //bananbread playlist id  
             if (!SongCore.Loader.AreSongsLoaded || SongCore.Loader.AreSongsLoading || playlist.playlistTitle == "All songs" || playlist.playlistTitle == "Your favorite songs") return;
 
+            Dictionary<string, CustomPreviewBeatmapLevel> songMap = new Dictionary<string, CustomPreviewBeatmapLevel>(StringComparer.OrdinalIgnoreCase);
+            foreach (var kp in SongCore.Loader.CustomLevels)
+            {
+                var songHash = CustomHelpers.GetSongHash(kp.Value.levelID);
+                if (songMap.ContainsKey(songHash))
+                {
+                    continue;
+                }
+
+                songMap.Add(songHash, kp.Value);
+            }
+
             if (!playlist.songs.All(x => x.level != null) || matchAll)
             {
                 playlist.songs.AsParallel().ForAll(x =>
@@ -152,22 +163,35 @@ namespace SongBrowser.DataAccess
                     {
                         try
                         {
-                            if (!string.IsNullOrEmpty(x.levelId)) //check that we have levelId and if we do, try to match level
+                            // try to use levelID
+                            if (!string.IsNullOrEmpty(x.levelId))
                             {
-                                x.level = SongCore.Loader.CustomLevels.Values.FirstOrDefault(y => y.levelID == x.levelId);
+                                string songHash = CustomHelpers.GetSongHash(x.levelId);
+                                if (songMap.ContainsKey(songHash))
+                                {
+                                    x.level = songMap[songHash];
+                                    x.hash = songHash;
+                                }
                             }
-                            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
+
+                            // failed, try again using hash
+                            if (x.level == null && !string.IsNullOrEmpty(x.hash))
                             {
+                                // fix broken playlists from a bug in song browser
                                 if (x.hash.Contains("custom_level"))
                                 {
                                     x.hash = CustomHelpers.GetSongHash(x.hash);
                                 }
-                                x.level = SongCore.Loader.CustomLevels.Values.FirstOrDefault(y => string.Equals(CustomHelpers.GetSongHash(y.levelID), x.hash, StringComparison.OrdinalIgnoreCase));
+
+                                if (songMap.ContainsKey(x.hash))
+                                {
+                                    x.level = songMap[x.hash];
+                                }
                             }
+
                             if (x.level == null && !string.IsNullOrEmpty(x.key))
                             {
                                 x.level = SongCore.Loader.CustomLevels.FirstOrDefault(y => y.Value.customLevelPath.Contains(x.key)).Value;
-
                                 if (x.level != null && !String.IsNullOrEmpty(x.level.levelID))
                                 {
                                     x.hash = CustomHelpers.GetSongHash(x.level.levelID);
@@ -217,29 +241,25 @@ namespace SongBrowser.DataAccess
             if (!string.IsNullOrEmpty(key) || level == null || !(level is CustomPreviewBeatmapLevel))
                 yield break;
 
+            string songHash = null;
             if (!string.IsNullOrEmpty(hash))
             {
-                ScrappedSong song = ScrappedData.Songs.FirstOrDefault(x => hash.ToUpper() == x.Hash);
-                if (song != null)
-                    key = song.Key;
-                else
-                    yield return SongDownloader.Instance.RequestSongByLevelIDCoroutine(hash, (Song bsSong) => { if (bsSong != null) key = bsSong.key; });
+                songHash = hash;
             }
             else if (!string.IsNullOrEmpty(levelId))
             {
-                ScrappedSong song = ScrappedData.Songs.FirstOrDefault(x => levelId.StartsWith(x.Hash));
-                if (song != null)
-                    key = song.Key;
-                else
-                    yield return SongDownloader.Instance.RequestSongByLevelIDCoroutine(level.levelID.Split('_')[2], (Song bsSong) => { if (bsSong != null) key = bsSong.key; });
+                songHash = CustomHelpers.GetSongHash(level.levelID);
             }
-            else if (level != null)
+            
+            if (songHash != null && SongDataCore.Plugin.BeatSaver.Data.Songs.ContainsKey(hash))
             {
-                ScrappedSong song = ScrappedData.Songs.FirstOrDefault(x => level.levelID.StartsWith(x.Hash));
-                if (song != null)
-                    key = song.Key;
-                else
-                    yield return SongDownloader.Instance.RequestSongByLevelIDCoroutine(level.levelID.Split('_')[2], (Song bsSong) => { if (bsSong != null) key = bsSong.key; });
+                var song = SongDataCore.Plugin.BeatSaver.Data.Songs[hash];
+                key = song.key;
+            }
+            else
+            {
+                // no more hitting api just to match a key.  We know the song hash.
+                //yield return SongDownloader.Instance.RequestSongByLevelIDCoroutine(level.levelID.Split('_')[2], (Song bsSong) => { if (bsSong != null) key = bsSong.key; });
             }
         }
     }
@@ -329,9 +349,8 @@ namespace SongBrowser.DataAccess
         }
 
         public void SavePlaylist(string path = "")
-        {
-            if (ScrappedData.Songs.Count > 0)
-                SharedCoroutineStarter.instance.StartCoroutine(SavePlaylistCoroutine(path));
+        {            
+            SharedCoroutineStarter.instance.StartCoroutine(SavePlaylistCoroutine(path));
         }
 
         public IEnumerator SavePlaylistCoroutine(string path = "")
@@ -354,9 +373,14 @@ namespace SongBrowser.DataAccess
                 Logger.Exception("Unable to save playlist! Exception: " + e);
                 yield break;
             }
-            foreach (PlaylistSong song in songs)
+
+            // match key if we can, not really that important anymore
+            if (SongDataCore.Plugin.BeatSaver.Data.Songs.Count > 0)
             {
-                yield return song.MatchKey();
+                foreach (PlaylistSong song in songs)
+                {
+                    yield return song.MatchKey();
+                }
             }
 
             try

+ 0 - 43
SongBrowserPlugin/DataAccess/ScoreSaberDatabase.cs

@@ -1,43 +0,0 @@
-using Newtonsoft.Json;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using Logger = SongBrowser.Logging.Logger;
-
-namespace SongBrowser.DataAccess
-{
-    public class ScoreSaberSong
-    {
-        public string song { get; set; }
-        public string mapper { get; set; }
-        public List<ScoreSaberSongDifficultyStats> diffs { get; set; }
-    }
-
-    public class ScoreSaberSongDifficultyStats
-    {
-        public string diff { get; set; }
-        public long scores { get; set; }
-        public double star { get; set; }
-        public double pp { get; set; }
-    }
-
-    public class ScoreSaberDataFile
-    {
-        public Dictionary<String, ScoreSaberSong> SongHashToScoreSaberData = null;
-
-        public ScoreSaberDataFile(byte[] data)
-        {
-            Stopwatch timer = new Stopwatch();
-            timer.Start();
-
-            System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint;
-
-            string result = System.Text.Encoding.UTF8.GetString(data);
-
-            SongHashToScoreSaberData = JsonConvert.DeserializeObject<Dictionary<string, ScoreSaberSong>>(result);
-                        
-            timer.Stop();
-            Logger.Debug("Processing ScoreSaber data took {0}ms", timer.ElapsedMilliseconds);
-        }
-    }
-}

+ 0 - 110
SongBrowserPlugin/DataAccess/ScrappedData.cs

@@ -1,110 +0,0 @@
-using Newtonsoft.Json;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using UnityEngine;
-using UnityEngine.Networking;
-using Logger = SongBrowser.Logging.Logger;
-
-namespace SongBrowser.DataAccess
-{
-    public class ScrappedSong
-    {
-        public string Key { get; set; }
-        public string Hash { get; set; }
-        public string SongName { get; set; }
-        public string SongSubName { get; set; }
-        public string LevelAuthorName { get; set; }
-        public string SongAuthorName { get; set; }
-        public List<DifficultyStats> Diffs { get; set; }
-        public long Bpm { get; set; }
-        public long PlayedCount { get; set; }
-        public long Upvotes { get; set; }
-        public long Downvotes { get; set; }
-        public float Heat { get; set; }
-        public float Rating { get; set; }
-    }
-
-    public class DifficultyStats
-    {
-        public string Diff { get; set; }
-        public long Scores { get; set; }
-        public double Stars { get; set; }
-    }
-
-    public class ScrappedData : MonoBehaviour
-    {
-        private static ScrappedData _instance = null;
-        public static ScrappedData Instance
-        {
-            get
-            {
-                if (!_instance)
-                {
-                    _instance = new GameObject("ScrappedData").AddComponent<ScrappedData>();
-                    DontDestroyOnLoad(_instance.gameObject);
-                }
-                return _instance;
-            }
-            private set
-            {
-                _instance = value;
-            }
-        }
-
-        public static List<ScrappedSong> Songs = new List<ScrappedSong>();
-
-        public static string scrappedDataURL = "https://raw.githubusercontent.com/andruzzzhka/BeatSaberScrappedData/master/combinedScrappedData.json";
-
-        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.Error(e);
-                yield break;
-            }
-
-            while (!asyncRequest.isDone)
-            {
-                yield return null;
-                time += Time.deltaTime;
-                if (time >= 5f && asyncRequest.progress <= float.Epsilon)
-                {
-                    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!");
-            }
-        }
-
-    }
-}

+ 92 - 77
SongBrowserPlugin/DataAccess/SongBrowserModel.cs

@@ -25,12 +25,9 @@ namespace SongBrowser
         private SongBrowserSettings _settings;
 
         // song list management
-        private double _customSongDirLastWriteTime = 0;
-        private Dictionary<String, CustomPreviewBeatmapLevel> _levelIdToCustomLevel;
+        private double _customSongDirLastWriteTime = 0;        
         private Dictionary<String, double> _cachedLastWriteTimes;
-        private Dictionary<string, int> _weights;
         private Dictionary<BeatmapDifficulty, int> _difficultyWeights;
-        private Dictionary<string, ScrappedSong> _levelHashToDownloaderData = null;
         private Dictionary<string, int> _levelIdToPlayCount;
 
         public BeatmapCharacteristicSO CurrentBeatmapCharacteristicSO;
@@ -105,38 +102,11 @@ namespace SongBrowser
         /// </summary>
         public SongBrowserModel()
         {
-            _levelIdToCustomLevel = new Dictionary<string, CustomPreviewBeatmapLevel>();
             _cachedLastWriteTimes = new Dictionary<String, double>();
             _levelIdToPlayCount = new Dictionary<string, int>();
 
             CurrentEditingPlaylistLevelIds = new HashSet<string>();
 
-            // Weights used for keeping the original songs in order
-            // Invert the weights from the game so we can order by descending and make LINQ work with us...
-            /*  Level4, Level2, Level9, Level5, Level10, Level6, Level7, Level1, Level3, Level8, Level11 */
-            _weights = new Dictionary<string, int>
-            {
-                ["OneHopeLevel"] = 12,
-                ["100Bills"] = 11,
-                ["Escape"] = 10,
-                ["Legend"] = 9,
-                ["BeatSaber"] = 8,
-                ["AngelVoices"] = 7,
-                ["CountryRounds"] = 6,
-                ["BalearicPumping"] = 5,
-                ["Breezer"] = 4,
-                ["CommercialPumping"] = 3,
-                ["TurnMeOn"] = 2,
-                ["LvlInsane"] = 1,
-
-                ["100BillsOneSaber"] = 12,
-                ["EscapeOneSaber"] = 11,
-                ["LegendOneSaber"] = 10,
-                ["BeatSaberOneSaber"] = 9,
-                ["CommercialPumpingOneSaber"] = 8,
-                ["TurnMeOnOneSaber"] = 8,
-            };
-
             _difficultyWeights = new Dictionary<BeatmapDifficulty, int>
             {
                 [BeatmapDifficulty.Easy] = 1,
@@ -204,11 +174,6 @@ namespace SongBrowser
                     double lastWriteTime = GetSongUserDate(level.Value);
                     _cachedLastWriteTimes[level.Value.levelID] = lastWriteTime;
                 }
-
-                if (!_levelIdToCustomLevel.ContainsKey(level.Value.levelID))
-                {
-                    _levelIdToCustomLevel.Add(level.Value.levelID, level.Value);
-                }
             }
 
             lastWriteTimer.Stop();
@@ -220,7 +185,7 @@ namespace SongBrowser
             // Check if we need to upgrade settings file favorites
             try
             {
-                this.Settings.ConvertFavoritesToPlaylist(_levelIdToCustomLevel);
+                this.Settings.ConvertFavoritesToPlaylist(SongCore.Loader.CustomLevels);
             }
             catch (Exception e)
             {
@@ -339,24 +304,6 @@ namespace SongBrowser
         }
         
         /// <summary>
-        /// Map the downloader data for quick lookup.
-        /// </summary>
-        /// <param name="songs"></param>
-        public void UpdateDownloaderDataMapping(List<ScrappedSong> songs)
-        {
-            _levelHashToDownloaderData = new Dictionary<string, ScrappedSong>();
-            foreach (ScrappedSong song in songs)
-            {
-                if (_levelHashToDownloaderData.ContainsKey(song.Hash))
-                {
-                    continue;
-                }
-
-                _levelHashToDownloaderData.Add(song.Hash, song);
-            }
-        }
-
-        /// <summary>
         /// Add Song to Editing Playlist
         /// </summary>
         /// <param name="songInfo"></param>
@@ -485,6 +432,12 @@ namespace SongBrowser
                 case SongFilterMode.Playlist:
                     filteredSongs = FilterPlaylist(pack);
                     break;
+                case SongFilterMode.Ranked:
+                    filteredSongs = FilterRanked(unsortedSongs, true, false);
+                    break;
+                case SongFilterMode.Unranked:
+                    filteredSongs = FilterRanked(unsortedSongs, false, true);
+                    break;
                 case SongFilterMode.Custom:
                     Logger.Info("Song filter mode set to custom. Deferring filter behaviour to another mod.");
                     filteredSongs = CustomFilterHandler != null ? CustomFilterHandler.Invoke(pack) : unsortedSongs;
@@ -523,6 +476,9 @@ namespace SongBrowser
                 case SongSortMode.Rating:
                     sortedSongs = SortBeatSaverRating(filteredSongs);
                     break;
+                case SongSortMode.Heat:
+                    sortedSongs = SortBeatSaverHeat(filteredSongs);
+                    break;
                 case SongSortMode.YourPlayCount:
                     sortedSongs = SortPlayCount(filteredSongs);
                     break;
@@ -655,6 +611,35 @@ namespace SongBrowser
         }
 
         /// <summary>
+        /// Filter songs based on ranked or unranked status.
+        /// </summary>
+        /// <param name="levels"></param>
+        /// <param name="includeRanked"></param>
+        /// <param name="includeUnranked"></param>
+        /// <returns></returns>
+        private List<IPreviewBeatmapLevel> FilterRanked(List<IPreviewBeatmapLevel> levels, bool includeRanked, bool includeUnranked)
+        {
+            return levels.Where(x =>
+            {
+                var hash = CustomHelpers.GetSongHash(x.levelID);
+                double maxPP = 0.0;
+                if (SongDataCore.Plugin.ScoreSaber.Data.Songs.ContainsKey(hash))
+                {
+                     maxPP = SongDataCore.Plugin.ScoreSaber.Data.Songs[hash].diffs.Max(y => y.pp);
+                }
+
+                if (maxPP > 0f)
+                {
+                    return includeRanked;
+                }
+                else
+                {
+                    return includeUnranked;
+                }
+            }).ToList();
+        }
+
+        /// <summary>
         /// Sorting returns original list.
         /// </summary>
         /// <param name="levels"></param>
@@ -674,8 +659,7 @@ namespace SongBrowser
         {
             Logger.Info("Sorting song list as newest.");
             return 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])
+                .OrderByDescending(x => _cachedLastWriteTimes.ContainsKey(x.levelID) ? _cachedLastWriteTimes[x.levelID] : int.MinValue)
                 .ToList();
         }
 
@@ -716,7 +700,7 @@ namespace SongBrowser
         {
             Logger.Info("Sorting song list by performance points...");
 
-            if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile == null)
+            if (!SongDataCore.Plugin.ScoreSaber.IsDataAvailable())
             {
                 return levels;
             }
@@ -725,9 +709,9 @@ namespace SongBrowser
                 .OrderByDescending(x =>
                 {
                     var hash = CustomHelpers.GetSongHash(x.levelID);
-                    if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData.ContainsKey(hash))
+                    if (SongDataCore.Plugin.ScoreSaber.Data.Songs.ContainsKey(hash))
                     {
-                        return ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData[hash].diffs.Max(y => y.pp);
+                        return SongDataCore.Plugin.ScoreSaber.Data.Songs[hash].diffs.Max(y => y.pp);
                     }
                     else
                     {
@@ -746,7 +730,7 @@ namespace SongBrowser
         {
             Logger.Info("Sorting song list by star points...");
 
-            if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile == null)
+            if (!SongDataCore.Plugin.ScoreSaber.IsDataAvailable())
             {
                 return levels;
             }
@@ -756,9 +740,9 @@ namespace SongBrowser
                 {
                     var hash = CustomHelpers.GetSongHash(x.levelID);
                     var stars = 0.0;
-                    if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData.ContainsKey(hash))
+                    if (SongDataCore.Plugin.ScoreSaber.Data.Songs.ContainsKey(hash))
                     {
-                        var diffs = ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData[hash].diffs;   
+                        var diffs = SongDataCore.Plugin.ScoreSaber.Data.Songs[hash].diffs;   
                         stars = diffs.Max(y => y.star);
                     }
 
@@ -787,8 +771,8 @@ namespace SongBrowser
         /// <returns></returns>
         private List<IPreviewBeatmapLevel> SortDifficulty(List<IPreviewBeatmapLevel> levels)
         {
-            Logger.Info("Sorting song list by difficulty...");
-
+            Logger.Info("Sorting song list by difficulty (DISABLED!!!)...");
+            /*
             IEnumerable<BeatmapDifficulty> difficultyIterator = Enum.GetValues(typeof(BeatmapDifficulty)).Cast<BeatmapDifficulty>();
             Dictionary<string, int> levelIdToDifficultyValue = new Dictionary<string, int>();
             foreach (IPreviewBeatmapLevel level in levels)
@@ -822,7 +806,8 @@ namespace SongBrowser
             return levels
                 .OrderBy(x => levelIdToDifficultyValue[x.levelID])
                 .ThenBy(x => x.songName)
-                .ToList();
+                .ToList();*/
+            return levels;
         }
 
         /// <summary>
@@ -865,7 +850,7 @@ namespace SongBrowser
             Logger.Info("Sorting song list by BeatSaver UpVotes");
 
             // Do not always have data when trying to sort by UpVotes
-            if (_levelHashToDownloaderData == null)
+            if (!SongDataCore.Plugin.BeatSaver.IsDataAvailable())
             {
                 return levelIds;
             }
@@ -873,9 +858,9 @@ namespace SongBrowser
             return levelIds
                 .OrderByDescending(x => {
                     var hash = CustomHelpers.GetSongHash(x.levelID);
-                    if (_levelHashToDownloaderData.ContainsKey(hash))
+                    if (SongDataCore.Plugin.BeatSaver.Data.Songs.ContainsKey(hash))
                     {
-                        return _levelHashToDownloaderData[hash].Upvotes;
+                        return SongDataCore.Plugin.BeatSaver.Data.Songs[hash].stats.upVotes;
                     }
                     else
                     {
@@ -895,7 +880,7 @@ namespace SongBrowser
             Logger.Info("Sorting song list by BeatSaver PlayCount");
 
             // Do not always have data when trying to sort by UpVotes
-            if (_levelHashToDownloaderData == null)
+            if (!SongDataCore.Plugin.BeatSaver.IsDataAvailable())
             {
                 return levelIds;
             }
@@ -903,9 +888,9 @@ namespace SongBrowser
             return levelIds
                 .OrderByDescending(x => {
                     var hash = CustomHelpers.GetSongHash(x.levelID);
-                    if (_levelHashToDownloaderData.ContainsKey(hash))
+                    if (SongDataCore.Plugin.BeatSaver.Data.Songs.ContainsKey(hash))
                     {
-                        return _levelHashToDownloaderData[hash].PlayedCount;
+                        return SongDataCore.Plugin.BeatSaver.Data.Songs[hash].stats.plays;
                     }
                     else
                     {
@@ -922,10 +907,40 @@ namespace SongBrowser
         /// <returns></returns>
         private List<IPreviewBeatmapLevel> SortBeatSaverRating(List<IPreviewBeatmapLevel> levelIds)
         {
-            Logger.Info("Sorting song list by BeatSaver Rating");
+            Logger.Info("Sorting song list by BeatSaver Rating!");
 
-            // Do not always have data when trying to sort by UpVotes
-            if (_levelHashToDownloaderData == null)
+            // Do not always have data when trying to sort by rating
+            if (!SongDataCore.Plugin.BeatSaver.IsDataAvailable())
+            {
+                return levelIds;
+            }
+
+            return levelIds
+                .OrderByDescending(x => {
+                    var hash = CustomHelpers.GetSongHash(x.levelID);
+                    if (SongDataCore.Plugin.BeatSaver.Data.Songs.ContainsKey(hash))
+                    {
+                        return SongDataCore.Plugin.BeatSaver.Data.Songs[hash].stats.rating;
+                    }
+                    else
+                    {
+                        return int.MinValue;
+                    }
+                })
+                .ToList();
+        }
+
+        /// <summary>
+        /// Sorting by BeatSaver heat stat.
+        /// </summary>
+        /// <param name="levelIds"></param>
+        /// <returns></returns>
+        private List<IPreviewBeatmapLevel> SortBeatSaverHeat(List<IPreviewBeatmapLevel> levelIds)
+        {
+            Logger.Info("Sorting song list by BeatSaver Heat!");
+
+            // Do not always have data when trying to sort by heat
+            if (!SongDataCore.Plugin.BeatSaver.IsDataAvailable())
             {
                 return levelIds;
             }
@@ -933,9 +948,9 @@ namespace SongBrowser
             return levelIds
                 .OrderByDescending(x => {
                     var hash = CustomHelpers.GetSongHash(x.levelID);
-                    if (_levelHashToDownloaderData.ContainsKey(hash))
+                    if (SongDataCore.Plugin.BeatSaver.Data.Songs.ContainsKey(hash))
                     {
-                        return _levelHashToDownloaderData[hash].Rating;
+                        return SongDataCore.Plugin.BeatSaver.Data.Songs[hash].stats.heat;
                     }
                     else
                     {

+ 46 - 2
SongBrowserPlugin/DataAccess/SongBrowserSettings.cs

@@ -24,6 +24,7 @@ namespace SongBrowser.DataAccess
         PP,
         UpVotes,
         Rating,
+        Heat,
         PlayCount,
         Stars,
 
@@ -33,6 +34,35 @@ namespace SongBrowser.DataAccess
         Search
     }
 
+    static class SongSortModeMethods
+    {
+        public static bool NeedsBeatSaverData(this SongSortMode s)
+        {
+            switch (s)
+            {
+                case SongSortMode.UpVotes:
+                case SongSortMode.Rating:
+                case SongSortMode.PlayCount:
+                case SongSortMode.Heat:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        public static bool NeedsScoreSaberData(this SongSortMode s)
+        {
+            switch (s)
+            {
+                case SongSortMode.PP:
+                case SongSortMode.Stars:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+    }
+
     [Serializable]
     public enum SongFilterMode
     {
@@ -40,6 +70,8 @@ namespace SongBrowser.DataAccess
         Favorites,
         Playlist,
         Search,
+        Ranked,
+        Unranked,
 
         // For other mods that extend SongBrowser
         Custom
@@ -223,8 +255,19 @@ namespace SongBrowser.DataAccess
         /// </summary>
         /// <param name="levelIdToCustomLevel"></param>
         /// <param name="levelIdToSongVersion"></param>
-        public void ConvertFavoritesToPlaylist(Dictionary<String, CustomPreviewBeatmapLevel> levelIdToCustomLevel)
+        public void ConvertFavoritesToPlaylist(Dictionary<String, CustomPreviewBeatmapLevel> customSongsMap)
         {
+            // map songs in case we are converting a huge list
+            Dictionary<String, CustomPreviewBeatmapLevel> levelIdToCustomLevel = new Dictionary<string, CustomPreviewBeatmapLevel>(StringComparer.OrdinalIgnoreCase);
+            foreach (var kp in customSongsMap)
+            {
+                if (levelIdToCustomLevel.ContainsKey(kp.Value.levelID))
+                {
+                    continue;
+                }
+                levelIdToCustomLevel.Add(kp.Value.levelID, kp.Value);
+            }
+
             // Check if we have favorites to convert to the playlist
             if (this.Favorites.Count <= 0)
             {
@@ -283,7 +326,8 @@ namespace SongBrowser.DataAccess
                 else
                 {
                     // No easy way to look up original songs... They will still work but have wrong song name in playlist.  
-                    playlistSong.songName = levelId;
+                    playlistSong.levelId = levelId;
+                    playlistSong.hash = CustomHelpers.GetSongHash(levelId);
                     playlistSong.key = "";
                 }
 

+ 7 - 36
SongBrowserPlugin/Logging/Logger.cs

@@ -35,26 +35,12 @@ namespace SongBrowser.Logging
 
         public static void Debug(string format, params object[] args)
         {
-            if (LogLevel > LogLevel.Debug)
-            {
-                return;
-            }
-
-            Console.ForegroundColor = ConsoleColor.Magenta;
-            Console.WriteLine("[" + LoggerName + " @ " + DateTime.Now.ToString("HH:mm") + " - Debug] " + String.Format(format, args));
-            ResetForegroundColor();
+            Plugin.Log.Debug(String.Format(format, args));
         }
 
         public static void Info(string format, params object[] args)
         {
-            if (LogLevel > LogLevel.Info)
-            {
-                return;
-            }
-
-            Console.ForegroundColor = ConsoleColor.Green;
-            Console.WriteLine("[" + LoggerName + " @ " + DateTime.Now.ToString("HH:mm") + " - Info] " + String.Format(format, args));
-            ResetForegroundColor();
+            Plugin.Log.Info(String.Format(format, args));
         }
 
         public static void Log(string format, params object[] args)
@@ -64,14 +50,7 @@ namespace SongBrowser.Logging
 
         public static void Warning(string format, params object[] args)
         {
-            if (LogLevel > LogLevel.Warn)
-            {
-                return;
-            }
-
-            Console.ForegroundColor = ConsoleColor.Blue;
-            Console.WriteLine("[" + LoggerName + " @ " + DateTime.Now.ToString("HH:mm") + " - Warning] " + String.Format(format, args));
-            ResetForegroundColor();
+            Plugin.Log.Warn(String.Format(format, args));
         }
 
         public static void Error(Exception e)
@@ -81,30 +60,22 @@ namespace SongBrowser.Logging
 
         public static void Error(string format, params object[] args)
         {
-            Console.ForegroundColor = ConsoleColor.Yellow;
-            Console.WriteLine("[" + LoggerName + " @ " + DateTime.Now.ToString("HH:mm") + " - Error] " + String.Format(format, args));
-            ResetForegroundColor();
+            Plugin.Log.Error(String.Format(format, args));
         }
 
         public static void Exception(string message)
         {
-            Console.ForegroundColor = ConsoleColor.Red;
-            Console.WriteLine("[" + LoggerName + " @ " + DateTime.Now.ToString("HH:mm") + "] " + String.Format("{0}", message));
-            ResetForegroundColor();
+            Plugin.Log.Critical(message);
         }
 
         public static void Exception(Exception e)
         {
-            Console.ForegroundColor = ConsoleColor.Red;
-            Console.WriteLine("[" + LoggerName + " @ " + DateTime.Now.ToString("HH:mm") + "] " + String.Format("{0}", e));
-            ResetForegroundColor();
+            Plugin.Log.Critical(e);
         }
 
         public static void Exception(string message, Exception e)
         {
-            Console.ForegroundColor = ConsoleColor.Red;
-            Console.WriteLine("[" + LoggerName + " @ " + DateTime.Now.ToString("HH:mm") + "] " + String.Format("{0}-{1}-{2}\n{3}", message, e.GetType().FullName, e.Message, e.StackTrace));
-            ResetForegroundColor();
+            Plugin.Log.Warn($"{message}:{e}");
         }
     }
 }

+ 12 - 24
SongBrowserPlugin/Plugin.cs

@@ -6,32 +6,25 @@ using SongBrowser.DataAccess;
 using System.Collections.Generic;
 using SongBrowser.Internals;
 using System;
-
+using IPA;
 
 namespace SongBrowser
 {
-    public class Plugin : IPlugin
+    public class Plugin : IBeatSaberPlugin
     {
-        public const string VERSION_NUMBER = "5.2.2";
+        public const string VERSION_NUMBER = "5.3.0";
         public static Plugin Instance;
+        public static IPA.Logging.Logger Log;
 
-        public string Name
-        {
-            get { return "Song Browser"; }
-        }
-
-        public string Version
+        public void Init(object nullObject, IPA.Logging.Logger logger)
         {
-            get { return VERSION_NUMBER; }
+            Log = logger;
         }
 
         public void OnApplicationStart()
         {
             Instance = this;
 
-            SceneManager.sceneLoaded += SceneManager_sceneLoaded;
-            SceneManager.activeSceneChanged += SceneManager_activeSceneChanged;
-
             PluginConfig.LoadOrCreateConfig();
 
             Base64Sprites.Init();
@@ -71,32 +64,27 @@ namespace SongBrowser
             }
         }
 
-        private void SceneManager_activeSceneChanged(Scene from, Scene to)
+        public void OnUpdate()
         {
-            Logger.Debug($"Active scene changed from \"{from.name}\" to \"{to.name}\"");
-        }
 
-        private void SceneManager_sceneLoaded(Scene to, LoadSceneMode loadMode)
-        {
-            Logger.Debug($"Loaded scene \"{to.name}\"");
         }
 
-        public void OnLevelWasLoaded(int level)
+        public void OnFixedUpdate()
         {
 
         }
 
-        public void OnLevelWasInitialized(int level)
+        public void OnSceneLoaded(Scene scene, LoadSceneMode sceneMode)
         {
-
+ 
         }
 
-        public void OnUpdate()
+        public void OnSceneUnloaded(Scene scene)
         {
 
         }
 
-        public void OnFixedUpdate()
+        public void OnActiveSceneChanged(Scene prevScene, Scene nextScene)
         {
 
         }

+ 3 - 3
SongBrowserPlugin/Properties/AssemblyInfo.cs

@@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
 // set of attributes. Change these attribute values to modify the information
 // associated with an assembly.
 [assembly: AssemblyTitle("SongBrowser")]
-[assembly: AssemblyDescription("")]
+[assembly: AssemblyDescription("BeatSaber Plugin")]
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("")]
 [assembly: AssemblyProduct("SongBrowser")]
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers 
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("5.2.2")]
-[assembly: AssemblyFileVersion("5.2.2")]
+[assembly: AssemblyVersion("5.3.0")]
+[assembly: AssemblyFileVersion("5.3.0")]

+ 8 - 7
SongBrowserPlugin/SongBrowser.csproj

@@ -69,6 +69,9 @@
     <Reference Include="SongCore">
       <HintPath>D:\Games\Steam\SteamApps\common\Beat Saber\Plugins\SongCore.dll</HintPath>
     </Reference>
+    <Reference Include="SongDataCore">
+      <HintPath>D:\Games\Steam\SteamApps\common\Beat Saber\Plugins\SongDataCore.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Data" />
     <Reference Include="System.IO.Compression" />
@@ -128,12 +131,8 @@
     <Compile Include="DataAccess\BeatSaverApi\BeatSaverApiResults.cs" />
     <Compile Include="DataAccess\FileOperationApiWrapper.cs" />
     <Compile Include="DataAccess\LoadScripts.cs" />
-    <Compile Include="DataAccess\Network\CacheableDownloadHandler.cs" />
-    <Compile Include="DataAccess\Network\CacheableDownloadHandlerScoreSaberData.cs" />
-    <Compile Include="DataAccess\Playlist.cs" />
-    <Compile Include="DataAccess\ScoreSaberDatabase.cs" />
+    <Compile Include="DataAccess\Playlist.cs" />
     <Compile Include="DataAccess\Network\Downloader.cs" />
-    <Compile Include="DataAccess\ScrappedData.cs" />
     <Compile Include="Internals\BSEvents.cs" />
     <Compile Include="Internals\CustomHelpers.cs" />
     <Compile Include="Internals\SimpleJSON.cs" />
@@ -143,7 +142,6 @@
     <Compile Include="UI\DownloadQueue\DownloadQueueTableCell.cs" />
     <Compile Include="UI\DownloadQueue\DownloadQueueViewController.cs" />
     <Compile Include="UI\ProgressBar.cs" />
-    <Compile Include="UI\ScoreSaberDatabaseDownloader.cs" />
     <Compile Include="SongBrowserApplication.cs" />
     <Compile Include="Plugin.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
@@ -177,6 +175,9 @@
   <ItemGroup>
     <None Include="packages.config" />
   </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="manifest.json" />
+  </ItemGroup>  
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
@@ -190,4 +191,4 @@ IF EXIST "C:\Program Files (x86)\Steam\steamapps\common\Beat Saber\Plugins" xcop
     <Target Name="AfterBuild">
     </Target>
     -->
-</Project>
+</Project>

+ 16 - 13
SongBrowserPlugin/SongBrowserApplication.cs

@@ -16,7 +16,6 @@ namespace SongBrowser
 
         // Song Browser UI Elements
         private SongBrowserUI _songBrowserUI;
-        private ScoreSaberDatabaseDownloader _ppDownloader;
 
         public static SongBrowser.UI.ProgressBar MainProgressBar;
 
@@ -48,8 +47,6 @@ namespace SongBrowser
             Instance = this;
 
             _songBrowserUI = gameObject.AddComponent<SongBrowserUI>();
-            _ppDownloader = gameObject.AddComponent<ScoreSaberDatabaseDownloader>();
-            _ppDownloader.onScoreSaberDataDownloaded += OnScoreSaberDataDownloaded;
         }
 
         /// <summary>
@@ -61,8 +58,8 @@ namespace SongBrowser
 
             InstallHandlers();
 
-            // Initialize Downloader Scrapped Data
-            StartCoroutine(ScrappedData.Instance.DownloadScrappedData(OnDownloaderScrappedDataDownloaded));
+            SongDataCore.Plugin.ScoreSaber.OnDataFinishedProcessing += OnScoreSaberDataDownloaded;
+            SongDataCore.Plugin.BeatSaver.OnDataFinishedProcessing += OnBeatSaverDataDownloaded;
 
             if (SongCore.Loader.AreSongsLoaded)
             {
@@ -114,10 +111,14 @@ namespace SongBrowser
             Logger.Trace("OnScoreSaberDataDownloaded");
             try
             {
-                if (_songBrowserUI.Model.Settings.sortMode == SongSortMode.PP)
+                if (_songBrowserUI.Model.Settings.sortMode.NeedsScoreSaberData())
                 {
                     _songBrowserUI.ProcessSongList();
-                    _songBrowserUI.RefreshSongList();
+                    _songBrowserUI.RefreshSongUI();
+                }
+                else
+                {
+                    _songBrowserUI.RefreshSortButtonUI();
                 }
             }
             catch (Exception e)
@@ -129,17 +130,19 @@ namespace SongBrowser
         /// <summary>
         /// Update mapping of scrapped song data.
         /// </summary>
-        private void OnDownloaderScrappedDataDownloaded(List<ScrappedSong> songs)
+        private void OnBeatSaverDataDownloaded()
         {
-            Logger.Trace("OnDownloaderScrappedDataDownloaded");
+            Logger.Trace("OnBeatSaverDataDownloaded");
             try
             {
-                PlaylistsCollection.MatchSongsForAllPlaylists(true);
-                _songBrowserUI.Model.UpdateDownloaderDataMapping(songs);
-                if (_songBrowserUI.Model.Settings.sortMode == SongSortMode.UpVotes)
+                if (_songBrowserUI.Model.Settings.sortMode.NeedsBeatSaverData())
                 {
                     _songBrowserUI.ProcessSongList();
-                    _songBrowserUI.RefreshSongList();
+                    _songBrowserUI.RefreshSongUI();
+                }
+                else
+                {
+                    _songBrowserUI.RefreshSortButtonUI();
                 }
             }
             catch (Exception e)

+ 103 - 73
SongBrowserPlugin/UI/Browser/SongBrowserUI.cs

@@ -13,6 +13,7 @@ using System.Collections;
 using SongCore.Utilities;
 using SongBrowser.Internals;
 using CustomUI.BeatSaber;
+using SongDataCore.ScoreSaber;
 
 namespace SongBrowser.UI
 {
@@ -151,13 +152,13 @@ namespace SongBrowser.UI
                 CreateDeleteButton();
                 CreateFastPageButtons();
 
-                RefreshSortButtonUI();
-
                 this.InstallHandlers();
 
                 this.ModifySongStatsPanel();
                 this.ResizeSongUI();
 
+                RefreshSortButtonUI();
+
                 _uiCreated = true;
                 Logger.Debug("Done Creating UI...");
             }
@@ -228,7 +229,8 @@ namespace SongBrowser.UI
 
             _filterByDisplay = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton", new Vector2(curX, buttonY), new Vector2(outerButtonWidth, buttonHeight), () =>
             {
-                CancelFilter();
+                _model.Settings.filterMode = SongFilterMode.None;
+                SongCore.Loader.Instance.RefreshLevelPacks();
                 RefreshSongUI();
             }, "");
             _filterByDisplay.SetButtonTextSize(displayButtonFontSize);
@@ -274,21 +276,21 @@ namespace SongBrowser.UI
         {
             Logger.Debug("Create sort buttons...");
 
-            float sortButtonFontSize = 2.25f;
+            float sortButtonFontSize = 2.15f;
             float sortButtonX = -23.0f;
-            float sortButtonWidth = 12.25f;
-            float buttonSpacing = 0.65f;
+            float sortButtonWidth = 12.0f;
+            float buttonSpacing = 0.25f;
             float buttonY = 37f;
             float buttonHeight = 5.0f;
 
             string[] sortButtonNames = new string[]
             {
-                    "Title", "Author", "Newest", "YourPlays", "PP", "Stars", "UpVotes", "PlayCount", "Rating"
+                    "Title", "Author", "Newest", "YourPlays", "PP", "Stars", "UpVotes", "PlayCount", "Rating", "Heat"
             };
 
             SongSortMode[] sortModes = new SongSortMode[]
             {
-                    SongSortMode.Default, SongSortMode.Author, SongSortMode.Newest, SongSortMode.YourPlayCount, SongSortMode.PP, SongSortMode.Stars,  SongSortMode.UpVotes, SongSortMode.PlayCount, SongSortMode.Rating
+                    SongSortMode.Default, SongSortMode.Author, SongSortMode.Newest, SongSortMode.YourPlayCount, SongSortMode.PP, SongSortMode.Stars,  SongSortMode.UpVotes, SongSortMode.PlayCount, SongSortMode.Rating, SongSortMode.Heat
             };
 
             _sortButtonGroup = new List<SongSortButton>();
@@ -306,7 +308,7 @@ namespace SongBrowser.UI
                     },
                     sortButtonNames[i]);
                 sortButton.Button.SetButtonTextSize(sortButtonFontSize);
-                sortButton.Button.GetComponentsInChildren<HorizontalLayoutGroup>().First(btn => btn.name == "Content").padding = new RectOffset(4, 4, 2, 2);
+                sortButton.Button.GetComponentsInChildren<HorizontalLayoutGroup>().First(btn => btn.name == "Content").padding = new RectOffset(4, 4, 2, 2);                
                 sortButton.Button.ToggleWordWrapping(false);
                 sortButton.Button.name = "Sort" + sortModes[i].ToString() + "Button";
 
@@ -328,32 +330,34 @@ namespace SongBrowser.UI
             float buttonY = 37f;
             float buttonHeight = 5.0f;
 
-            List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>> filterButtonSetup = new List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>>()
-                {
-                    Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Favorites, OnFavoriteFilterButtonClickEvent, Base64Sprites.StarFullIcon),
-                    Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Playlist, OnPlaylistButtonClickEvent, Base64Sprites.PlaylistIcon),
-                    Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Search, OnSearchButtonClickEvent, Base64Sprites.SearchIcon),
-                };
+            string[] filterButtonNames = new string[]
+            {
+                    "Favorites", "Playlist", "Search", "Ranked", "Unranked"
+            };
+
+            SongFilterMode[] filterModes = new SongFilterMode[]
+            {
+                    SongFilterMode.Favorites, SongFilterMode.Playlist, SongFilterMode.Search, SongFilterMode.Ranked, SongFilterMode.Unranked
+            };
 
             _filterButtonGroup = new List<SongFilterButton>();
-            for (int i = 0; i < filterButtonSetup.Count; i++)
+            for (int i = 0; i < filterButtonNames.Length; i++)
             {
-                Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite> t = filterButtonSetup[i];
                 float curButtonX = filterButtonX + (filterButtonWidth * i) + (buttonSpacing * i);
                 SongFilterButton filterButton = new SongFilterButton();
-                filterButton.FilterMode = t.Item1;
+                filterButton.FilterMode = filterModes[i];
                 filterButton.Button = _beatUi.LevelPackLevelsViewController.CreateUIButton("ApplyButton",
                     new Vector2(curButtonX, buttonY), new Vector2(filterButtonWidth, buttonHeight),
-                    t.Item2,
-                    t.Item1.ToString());
+                    () =>
+                    {
+                        OnFilterButtonClickEvent(filterButton.FilterMode);
+                        RefreshOuterUIState(UIState.Main);
+                    },
+                    filterButtonNames[i]);
                 filterButton.Button.SetButtonTextSize(filterButtonFontSize);
                 filterButton.Button.GetComponentsInChildren<HorizontalLayoutGroup>().First(btn => btn.name == "Content").padding = new RectOffset(4, 4, 2, 2);
                 filterButton.Button.ToggleWordWrapping(false);
-                filterButton.Button.onClick.AddListener(() =>
-                {
-                    RefreshOuterUIState(UIState.Main);
-                });
-                filterButton.Button.name = "Filter" + t.Item1.ToString() + "Button";
+                filterButton.Button.name = "Filter" + filterButtonNames[i] + "Button";
 
                 _filterButtonGroup.Add(filterButton);
             }
@@ -528,6 +532,11 @@ namespace SongBrowser.UI
         /// </summary>
         public void RefreshSongUI(bool scrollToLevel=true)
         {
+            if (!_uiCreated)
+            {
+                return;
+            }
+
             RefreshSongList(scrollToLevel);
             RefreshSortButtonUI();
             if (!scrollToLevel)
@@ -556,8 +565,9 @@ namespace SongBrowser.UI
         /// </summary>
         public void CancelFilter()
         {
+            Logger.Debug("Cancelling filter.");
             _model.Settings.filterMode = SongFilterMode.None;
-            SongCore.Loader.Instance.RefreshLevelPacks();            
+            SongCore.Loader.Instance.RefreshLevelPacks();
         }
 
         /// <summary>
@@ -662,9 +672,10 @@ namespace SongBrowser.UI
 
             _model.Settings.sortMode = SongSortMode.Original;
             _model.Settings.invertSortResults = false;
-            CancelFilter();
+            _model.Settings.filterMode = SongFilterMode.None;
             _model.Settings.Save();
 
+            SongCore.Loader.Instance.RefreshLevelPacks();
             ProcessSongList();
             RefreshSongUI();
         }
@@ -676,6 +687,13 @@ namespace SongBrowser.UI
         {
             Logger.Debug("Sort button - {0} - pressed.", sortMode.ToString());
 
+            if ((sortMode.NeedsScoreSaberData() && !SongDataCore.Plugin.ScoreSaber.IsDataAvailable()) ||
+                (sortMode.NeedsBeatSaverData() && !SongDataCore.Plugin.BeatSaver.IsDataAvailable()))
+            {
+                Logger.Info("Data for sort type is not available.");
+                return;
+            }
+
             // Clear current selected level id so our song list jumps to the start
             _model.LastSelectedLevelId = null;
 
@@ -696,54 +714,63 @@ namespace SongBrowser.UI
 
             ProcessSongList();
             RefreshSongUI();
-
-            // update the display
-            _sortByDisplay.SetButtonText(_model.Settings.sortMode.ToString());
         }
 
         /// <summary>
-        /// Filter by favorites.
+        /// Handle filter button logic.  Some filters have sub menus that need special logic.
         /// </summary>
-        private void OnFavoriteFilterButtonClickEvent()
+        /// <param name="mode"></param>
+        private void OnFilterButtonClickEvent(SongFilterMode mode)
         {
-            Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Favorites.ToString());
+            Logger.Debug($"FilterButton {mode} clicked.");
+
+            // TODO - Downloader level pack support - need a way to refresh downloader level packs.
+
+            // Every filter will lead to needing a refresh.
+            SongCore.Loader.Instance.RefreshLevelPacks();
 
-            if (_model.Settings.filterMode != SongFilterMode.Favorites)
+            // Always select the custom level pack - TODO - Downloader level pack support - Don't always do this
+            var pack = _beatUi.GetCurrentSelectedLevelPack();
+            if (pack != null && !pack.packID.Equals(PluginConfig.CUSTOM_SONG_LEVEL_PACK_ID))
             {
-                _model.Settings.filterMode = SongFilterMode.Favorites;
                 _beatUi.SelectLevelPack(PluginConfig.CUSTOM_SONG_LEVEL_PACK_ID);
             }
+
+            // If selecting the same filter, cancel
+            if (_model.Settings.filterMode == mode)
+            {
+                _model.Settings.filterMode = SongFilterMode.None;
+            }
             else
             {
-                CancelFilter();
+                _model.Settings.filterMode = mode;
             }
 
-            _model.Settings.Save();
-
-            ProcessSongList();
-            RefreshSongUI();
+            switch (mode)
+            {
+                case SongFilterMode.Playlist:
+                    OnPlaylistButtonClickEvent();
+                    break;
+                case SongFilterMode.Search:
+                    OnSearchButtonClickEvent();
+                    break;
+                default:
+                    _model.Settings.Save();
+                    ProcessSongList();
+                    RefreshSongUI();
+                    break;
+            }
         }
 
         /// <summary>
-        /// Filter button clicked.  
+        /// Display the keyboard.
         /// </summary>
         /// <param name="sortMode"></param>
         private void OnSearchButtonClickEvent()
         {
             Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Search.ToString());
-            if (_model.Settings.filterMode != SongFilterMode.Search)
-            {
-                _beatUi.SelectLevelPack(PluginConfig.CUSTOM_SONG_LEVEL_PACK_ID);
-                this.ShowSearchKeyboard();
-            }
-            else
-            {
-                CancelFilter();
-                ProcessSongList();
-                RefreshSongUI();
 
-                _model.Settings.Save();
-            }                        
+            this.ShowSearchKeyboard();
         }
 
         /// <summary>
@@ -754,22 +781,8 @@ namespace SongBrowser.UI
         {
             Logger.Debug("Filter button - {0} - pressed.", SongFilterMode.Playlist.ToString());
             _model.LastSelectedLevelId = null;
-
-            if (_model.Settings.filterMode != SongFilterMode.Playlist)
-            {
-                _beatUi.SelectLevelPack(PluginConfig.CUSTOM_SONG_LEVEL_PACK_ID);
-                _playListFlowCoordinator.parentFlowCoordinator = _beatUi.LevelSelectionFlowCoordinator;
-                _beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("PresentFlowCoordinator", new object[] { _playListFlowCoordinator, null, false, false });                                
-            }
-            else
-            {
-                CancelFilter();
-                
-                ProcessSongList();
-                RefreshSongUI();
-
-                _model.Settings.Save();
-            }
+            _playListFlowCoordinator.parentFlowCoordinator = _beatUi.LevelSelectionFlowCoordinator;
+            _beatUi.LevelSelectionFlowCoordinator.InvokePrivateMethod("PresentFlowCoordinator", new object[] { _playListFlowCoordinator, null, false, false });
         }
 
         /// <summary>
@@ -1080,7 +1093,7 @@ namespace SongBrowser.UI
         {
             Logger.Trace("RefreshScoreSaberData({0})", level.levelID);
 
-            if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile == null)
+            if (!SongDataCore.Plugin.ScoreSaber.IsDataAvailable())
             {
                 return;
             }
@@ -1096,10 +1109,10 @@ namespace SongBrowser.UI
             // Check if we have data for this song
             Logger.Debug("Checking if have info for song {0}", level.songName);
             var hash = CustomHelpers.GetSongHash(level.levelID);
-            if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData.ContainsKey(hash))
+            if (SongDataCore.Plugin.ScoreSaber.Data.Songs.ContainsKey(hash))
             {
                 Logger.Debug("Checking if have difficulty for song {0} difficulty {1}", level.songName, difficultyString);
-                ScoreSaberSong scoreSaberSong = ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData[hash];
+                ScoreSaberSong scoreSaberSong = SongDataCore.Plugin.ScoreSaber.Data.Songs[hash];
                 ScoreSaberSongDifficultyStats scoreSaberSongDifficulty = scoreSaberSong.diffs.FirstOrDefault(x => String.Equals(x.diff, difficultyString));
                 if (scoreSaberSongDifficulty != null)
                 {
@@ -1299,10 +1312,27 @@ namespace SongBrowser.UI
         /// </summary>
         public void RefreshSortButtonUI()
         {
+            if (!_uiCreated)
+            {
+                return;
+            }
+
             // So far all we need to refresh is the sort buttons.
             foreach (SongSortButton sortButton in _sortButtonGroup)
             {
-                UIBuilder.SetButtonBorder(sortButton.Button, Color.white);
+                if (sortButton.SortMode.NeedsBeatSaverData() && !SongDataCore.Plugin.BeatSaver.IsDataAvailable())
+                {
+                    UIBuilder.SetButtonBorder(sortButton.Button, Color.gray);
+                }
+                else if (sortButton.SortMode.NeedsScoreSaberData() && !SongDataCore.Plugin.ScoreSaber.IsDataAvailable())
+                {
+                    UIBuilder.SetButtonBorder(sortButton.Button, Color.gray);
+                }
+                else
+                {
+                    UIBuilder.SetButtonBorder(sortButton.Button, Color.white);
+                }
+
                 if (sortButton.SortMode == _model.Settings.sortMode)
                 {
                     if (this._model.Settings.invertSortResults)

+ 0 - 122
SongBrowserPlugin/UI/ScoreSaberDatabaseDownloader.cs

@@ -1,122 +0,0 @@
-using Mobcast.Coffee.AssetSystem;
-using SongBrowser.DataAccess;
-using SongBrowser.DataAccess.Network;
-using System;
-using System.Collections;
-using UnityEngine;
-using UnityEngine.Networking;
-using Logger = SongBrowser.Logging.Logger;
-
-namespace SongBrowser.UI
-{
-    public class ScoreSaberDatabaseDownloader : MonoBehaviour
-    {
-        public const String SCRAPED_SCORE_SABER_ALL_JSON_URL = "https://cdn.wes.cloud/beatstar/bssb/v2-all.json";
-        public const String SCRAPED_SCORE_SABER_RANKED_JSON_URL = "https://cdn.wes.cloud/beatstar/bssb/v2-ranked.json";
-
-        public static ScoreSaberDatabaseDownloader Instance;
-
-        public static ScoreSaberDataFile ScoreSaberDataFile = null;        
-
-        public Action onScoreSaberDataDownloaded;
-
-        private readonly byte[] _buffer = new byte[4 * 1048576];
-
-        /// <summary>
-        /// Awake.
-        /// </summary>
-        private void Awake()
-        {
-            Logger.Trace("Awake-ScoreSaberDatabaseDownloader()");
-
-            if (Instance == null)
-            {
-                Instance = this;
-            }
-        }
-
-        /// <summary>
-        /// Acquire any UI elements from Beat saber that we need.  Wait for the song list to be loaded.
-        /// </summary>
-        public void Start()
-        {
-            Logger.Trace("Start()");
-
-            StartCoroutine(DownloadScoreSaberDatabases());
-        }
-
-        /// <summary>
-        /// Helper to download both databases.
-        /// </summary>
-        /// <returns></returns>
-        private IEnumerator DownloadScoreSaberDatabases()
-        {
-            ScoreSaberDataFile = null;
-
-            yield return DownloadScoreSaberData(SCRAPED_SCORE_SABER_ALL_JSON_URL);
-            yield return DownloadScoreSaberData(SCRAPED_SCORE_SABER_RANKED_JSON_URL);
-
-            if (ScoreSaberDataFile != null)
-            {
-                SongBrowserApplication.MainProgressBar.ShowMessage("Success downloading BeatStar data...", 5.0f);
-                onScoreSaberDataDownloaded?.Invoke();
-            }
-        }
-
-        /// <summary>
-        /// Wait for score saber related files to download.
-        /// </summary>
-        /// <returns></returns>
-        private IEnumerator DownloadScoreSaberData(String url)
-        {
-            SongBrowserApplication.MainProgressBar.ShowMessage("Downloading BeatStar data...", 5.0f);
-
-            Logger.Info("Attempting to download: {0}", url);
-            using (UnityWebRequest www = UnityWebRequest.Get(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
-                {
-                    // First time 
-                    if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile == null)
-                    {
-                        ScoreSaberDatabaseDownloader.ScoreSaberDataFile = (www.downloadHandler as CacheableDownloadHandlerScoreSaberData).ScoreSaberDataFile;                            
-                    }
-                    else
-                    {
-                        // Second time, update.
-                        var newScoreSaberData = (www.downloadHandler as CacheableDownloadHandlerScoreSaberData).ScoreSaberDataFile;
-                        foreach (var pair in newScoreSaberData.SongHashToScoreSaberData)
-                        {
-                            if (ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData.ContainsKey(pair.Key))
-                            {
-                                foreach (var diff in pair.Value.diffs)
-                                {
-                                    var index = ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData[pair.Key].diffs.FindIndex(x => x.diff == diff.diff);
-                                    if (index < 0)
-                                    {
-                                        ScoreSaberDatabaseDownloader.ScoreSaberDataFile.SongHashToScoreSaberData[pair.Key].diffs.Add(diff);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    Logger.Info("Success downloading ScoreSaber data!");
-                }
-                catch (System.InvalidOperationException)
-                {
-                    Logger.Error("Failed to download ScoreSaber data file...");
-                }
-                catch (Exception e)
-                {
-                    Logger.Exception("Exception trying to download ScoreSaber data file...", e);
-                }
-            }
-        }
-    }
-}

+ 20 - 0
SongBrowserPlugin/manifest.json

@@ -0,0 +1,20 @@
+{
+  "$schema": "https://raw.githubusercontent.com/beat-saber-modding-group/BSIPA-MetadataFileSchema/master/Schema.json",
+  "author": "Halsafar",
+  "description": "Adds sort and filter features to the level selection UI.",
+  "gameVersion": "1.1.0",
+  "id": "SongBrowser",
+  "name": "Song Browser",
+  "version": "5.3.0",
+  "dependsOn": {
+    "CustomUI": "^1.5.4",
+    "SongCore": "^2.0.0",
+    "SongDataCore":  "^1.0.0"
+  },
+  "features": [
+    "print",
+    "debug",
+    "warn",
+    "config-provider"
+  ]
+}