diff --git a/NuGet.Config b/NuGet.Config
index 3228f75..73f2d67 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -1,8 +1,6 @@
-
+
-
-
-
\ No newline at end of file
+
diff --git a/source/SongCore/Loader.cs b/source/SongCore/Loader.cs
index b67ed0e..8f426e0 100644
--- a/source/SongCore/Loader.cs
+++ b/source/SongCore/Loader.cs
@@ -146,7 +146,7 @@ private void HandleSceneTransitionDidFinish(SceneTransitionType sceneTransitionT
defaultCoverImage = _levelPackDetailViewController._defaultCoverSprite;
beatmapCharacteristicCollection = _beatmapCharacteristicCollection;
- if (Hashing.cachedSongHashData.Count == 0)
+ if (BinaryCache.Count == 0 && Hashing.cachedSongHashData.Count == 0)
{
Hashing.ReadCachedSongHashes();
Hashing.ReadCachedAudioData();
@@ -337,6 +337,8 @@ private async void RetrieveAllSongs(bool fullRefresh)
#endregion
+ var fastPathCount = 0;
+ var slowPathCount = 0;
ConcurrentDictionary foundSongPaths = fullRefresh
? new ConcurrentDictionary()
: new ConcurrentDictionary(Hashing.cachedSongHashData.Keys.ToDictionary(Hashing.GetAbsolutePath, _ => false));
@@ -446,9 +448,10 @@ void AddOfficialBeatmapLevelsRepository(BeatmapLevelsRepository levelsRepository
.Select(d => d.FullName)
.ToArray();
var songFoldersCount = songFolders.Length;
+ // cap parallelism so we don't starve the main thread
var parallelOptions = new ParallelOptions
{
- MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2 - 1),
+ MaxDegreeOfParallelism = Math.Min(8, Math.Max(2, Environment.ProcessorCount / 2)),
CancellationToken = _loadingTaskCancellationTokenSource.Token
};
var processedSongsCount = 0;
@@ -481,48 +484,80 @@ void AddOfficialBeatmapLevelsRepository(BeatmapLevelsRepository levelsRepository
Parallel.ForEach(songFolders, parallelOptions, folder =>
{
- string[] results;
try
{
- results = Directory.GetFiles(folder, CustomLevelPathHelper.kStandardLevelInfoFilename, SearchOption.TopDirectoryOnly);
- }
- catch (Exception ex)
- {
- Plugin.Log.Warn($"Skipping missing or corrupt folder: '{folder}'");
- Plugin.Log.Warn(ex);
- return;
- }
+ var songPath = folder;
+ if (Directory.GetParent(songPath)?.Name == "Backups")
+ {
+ return;
+ }
- if (results.Length == 0)
- {
- Plugin.Log.Warn($"Folder: '{folder}' is missing {CustomLevelPathHelper.kStandardLevelInfoFilename} file!");
- return;
- }
+ if (!fullRefresh && (CustomLevels.ContainsKey(songPath) || CustomWIPLevels.ContainsKey(songPath)))
+ {
+ LoadingProgress = (float) Interlocked.Increment(ref processedSongsCount) / songFoldersCount;
+ return;
+ }
- foreach (var result in results)
- {
+ Hashing.TryGetRelativePath(songPath, out var relativePath);
+ long dirTimestamp;
try
{
- var songPath = Path.GetDirectoryName(result)!;
- if (Directory.GetParent(songPath)?.Name == "Backups")
- {
- continue;
- }
+ dirTimestamp = Directory.GetLastWriteTimeUtc(songPath).ToFileTimeUtc();
+ }
+ catch
+ {
+ Plugin.Log.Warn($"Skipping missing or corrupt folder: '{folder}'");
+ LoadingProgress = (float) Interlocked.Increment(ref processedSongsCount) / songFoldersCount;
+ return;
+ }
- if (!fullRefresh && (CustomLevels.ContainsKey(songPath) || CustomWIPLevels.ContainsKey(songPath)))
- {
- continue;
- }
+ BinaryCache.CacheEntry cached = null;
+ bool hasCacheHit = BinaryCache.TryGetValid(relativePath, dirTimestamp, out cached);
- var wip = songPath.Contains("CustomWIPLevels");
- var customLevel = LoadCustomLevel(songPath);
- if (!customLevel.HasValue)
+ if (hasCacheHit && cached != null
+ && !string.IsNullOrEmpty(cached.SongHash) && !string.IsNullOrEmpty(cached.InfoDatJson))
+ {
+
+ Interlocked.Increment(ref fastPathCount);
+ var reconstructed = ReconstructFromCache(songPath, cached);
+ if (reconstructed.HasValue)
{
- Plugin.Log.Error($"Failed to load custom level: {folder}");
- continue;
+ var (_, level) = reconstructed.Value;
+ var wip = songPath.Contains("CustomWIPLevels");
+ if (!wip)
+ {
+ CustomLevelsById[level.levelID] = level;
+ CustomLevels[songPath] = level;
+ }
+ else
+ {
+ CustomWIPLevels[songPath] = level;
+ }
+ foundSongPaths.TryAdd(songPath, false);
+ LoadingProgress = (float) Interlocked.Increment(ref processedSongsCount) / songFoldersCount;
+ return;
}
+ }
+
+ if (!File.Exists(Path.Combine(songPath, CustomLevelPathHelper.kStandardLevelInfoFilename)))
+ {
+ Plugin.Log.Warn($"Folder: '{folder}' is missing {CustomLevelPathHelper.kStandardLevelInfoFilename} file!");
+ LoadingProgress = (float) Interlocked.Increment(ref processedSongsCount) / songFoldersCount;
+ return;
+ }
+
+ Interlocked.Increment(ref slowPathCount);
+ var customLevel = LoadCustomLevel(songPath);
+ if (!customLevel.HasValue)
+ {
+ Plugin.Log.Error($"Failed to load custom level: {folder}");
+ LoadingProgress = (float) Interlocked.Increment(ref processedSongsCount) / songFoldersCount;
+ return;
+ }
+ {
var (_, level) = customLevel.Value;
+ var wip = songPath.Contains("CustomWIPLevels");
if (!wip)
{
CustomLevelsById[level.levelID] = level;
@@ -532,14 +567,13 @@ void AddOfficialBeatmapLevelsRepository(BeatmapLevelsRepository levelsRepository
{
CustomWIPLevels[songPath] = level;
}
-
foundSongPaths.TryAdd(songPath, false);
}
- catch (Exception e)
- {
- Plugin.Log.Error($"Failed to load song folder: {result}");
- Plugin.Log.Error(e);
- }
+ }
+ catch (Exception e)
+ {
+ Plugin.Log.Error($"Failed to load song folder: {folder}");
+ Plugin.Log.Error(e);
}
LoadingProgress = (float) Interlocked.Increment(ref processedSongsCount) / songFoldersCount;
@@ -677,7 +711,7 @@ void AddOfficialBeatmapLevelsRepository(BeatmapLevelsRepository levelsRepository
int folderCount = songCount - songCountWSF;
string songOrSongs = songCount == 1 ? "song" : "songs";
string folderOrFolders = folderCount == 1 ? "folder" : "folders";
- Plugin.Log.Info($"Loaded {songCount} new {songOrSongs} ({songCountWSF}) in CustomLevels | {folderCount} in separate {folderOrFolders}) in {stopwatch.Elapsed.TotalSeconds} seconds");
+ Plugin.Log.Info($"Loaded {songCount} new {songOrSongs} ({songCountWSF}) in CustomLevels | {folderCount} in separate {folderOrFolders}) in {stopwatch.Elapsed.TotalSeconds} seconds (fast:{fastPathCount} slow:{slowPathCount})");
try
{
#region AddSeparateFolderBeatmapsToRespectivePacks
@@ -745,10 +779,14 @@ await UnityMainThreadTaskScheduler.Factory.StartNew(() =>
_loadingTask = null;
await UnityMainThreadTaskScheduler.Factory.StartNew(() => SongsLoadedEvent?.Invoke(this, CustomLevels));
- // Write our cached hash info and
Hashing.UpdateCachedHashesInternal(foundSongPaths.Keys);
Hashing.UpdateCachedAudioDataInternal(foundSongPaths.Keys);
- await Collections.SaveCustomLevelSongDataAsync();
+
+ if (fullRefresh)
+ {
+ BinaryCache.SaveAndPrune(foundSongPaths.Keys);
+ await Collections.SaveCustomLevelSongDataAsync();
+ }
};
try
@@ -910,6 +948,16 @@ private void DeleteSingleSong(string folderPath, bool deleteFolder)
Accessors.LevelIDAccessor(ref beatmapLevel) = levelID;
GetSongDuration(loadedSaveData, beatmapLevel);
+
+ Hashing.TryGetRelativePath(loadedSaveData.customLevelFolderInfo.folderPath, out var cacheRelPath);
+ var cacheEntry = BinaryCache.TryGet(cacheRelPath, out var existing) ? existing : new BinaryCache.CacheEntry();
+ cacheEntry.InfoDatJson = loadedSaveData.customLevelFolderInfo.levelInfoJsonString;
+ cacheEntry.LevelId = levelID;
+ if (Collections.CustomSongsData.TryGetValue(levelID, out var cachedSongData))
+ {
+ cacheEntry.SongDataJson = JsonConvert.SerializeObject(cachedSongData);
+ }
+ BinaryCache.Set(cacheRelPath, cacheEntry);
}
catch (Exception e)
{
@@ -1063,6 +1111,108 @@ private bool AssignBeatmapToSeparateFolder(
return false;
}
+
+ private (string hash, BeatmapLevel beatmapLevel)? ReconstructFromCache(string songPath, BinaryCache.CacheEntry cached)
+ {
+ try
+ {
+ var directoryInfo = new DirectoryInfo(songPath);
+ var json = cached.InfoDatJson;
+ var customLevelFolderInfo = new CustomLevelFolderInfo(directoryInfo.FullName, directoryInfo.Name, json);
+
+ CustomLevelLoader.LoadedSaveData loadedSaveData;
+ BeatmapLevel? beatmapLevel;
+
+ var version = BeatmapSaveDataHelpers.GetVersion(json);
+ if (version < BeatmapSaveDataHelpers.version4)
+ {
+ var standardLevelInfoSaveData = StandardLevelInfoSaveData.DeserializeFromJSONString(json);
+ if (standardLevelInfoSaveData == null) return null;
+
+ loadedSaveData = new CustomLevelLoader.LoadedSaveData { customLevelFolderInfo = customLevelFolderInfo, standardLevelInfoSaveData = standardLevelInfoSaveData };
+ beatmapLevel = _customLevelLoader.CreateBeatmapLevelFromV3(customLevelFolderInfo, standardLevelInfoSaveData);
+ }
+ else
+ {
+ var beatmapLevelSaveData = JsonConvert.DeserializeObject(json, JsonSettings.readableWithDefault);
+ if (beatmapLevelSaveData == null) return null;
+ BeatmapLevelSaveDataUtils.MigrateBeatmapLevelSaveData(beatmapLevelSaveData);
+ loadedSaveData = new CustomLevelLoader.LoadedSaveData { customLevelFolderInfo = customLevelFolderInfo, beatmapLevelSaveData = beatmapLevelSaveData };
+ beatmapLevel = _customLevelLoader.CreateBeatmapLevelFromV4(customLevelFolderInfo, beatmapLevelSaveData);
+ }
+
+ var hash = cached.SongHash;
+ var wip = songPath.Contains("CustomWIPLevels");
+
+ string levelID = CustomLevelLoader.kCustomLevelPrefixId + hash;
+ string folderName = directoryInfo.Name;
+ while (!Collections.LevelHashDictionary.TryAdd(levelID + (wip ? " WIP" : ""), hash))
+ {
+ levelID += $"_{folderName}";
+ }
+
+ if (wip)
+ {
+ levelID += " WIP";
+ }
+
+ Collections.HashLevelDictionary.AddOrUpdate(hash, new List { levelID }, (_, levels) =>
+ {
+ lock (levels)
+ {
+ levels.Add(levelID);
+ }
+ return levels;
+ });
+
+ Accessors.LevelIDAccessor(ref beatmapLevel) = levelID;
+
+ if (cached.Duration > 0)
+ {
+ Accessors.SongDurationAccessor(ref beatmapLevel) = cached.Duration;
+ }
+ else
+ {
+ GetSongDuration(loadedSaveData, beatmapLevel);
+ }
+
+ LoadedBeatmapSaveData.TryAdd(levelID, loadedSaveData);
+
+ Hashing.TryGetRelativePath(songPath, out var cacheRelPath);
+ Hashing.cachedSongHashData[cacheRelPath] = new SongHashData(cached.DirTimestamp, hash);
+
+ if (cached.Duration > 0)
+ {
+ Hashing.cachedAudioData[cacheRelPath] = new AudioCacheData(levelID, cached.Duration);
+ }
+
+ if (!string.IsNullOrEmpty(cached.SongDataJson))
+ {
+ try
+ {
+ var songData = JsonConvert.DeserializeObject(cached.SongDataJson);
+ if (songData != null)
+ {
+ Collections.CustomSongsData.TryAdd(levelID, songData);
+ }
+ }
+ catch { }
+ }
+
+ if (!Collections.CustomSongsData.ContainsKey(levelID))
+ {
+ Collections.CreateCustomLevelSongData(levelID, loadedSaveData);
+ }
+
+ return (hash, beatmapLevel);
+ }
+ catch (Exception ex)
+ {
+ Plugin.Log.Warn($"Cache reconstruction failed for '{songPath}': {ex.Message}");
+ return null;
+ }
+ }
+
public static (string hash, BeatmapLevel beatmapLevel)? LoadCustomLevel(string customLevelPath, SongFolderEntry? entry = null)
{
return Instance.LoadCustomLevelInternal(customLevelPath, entry);
diff --git a/source/SongCore/Utilities/BinaryCache.cs b/source/SongCore/Utilities/BinaryCache.cs
new file mode 100644
index 0000000..e166c8f
--- /dev/null
+++ b/source/SongCore/Utilities/BinaryCache.cs
@@ -0,0 +1,283 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Newtonsoft.Json;
+using SongCore.Data;
+
+namespace SongCore.Utilities
+{
+ internal static class BinaryCache
+ {
+ private const string Magic = "SC02";
+ private const int FormatVersion = 2;
+
+ internal static readonly string CachePath = Path.Combine(
+ IPA.Utilities.UnityGame.UserDataPath, nameof(SongCore), "SongCoreCache.bin");
+
+
+ internal class CacheEntry
+ {
+ public string RelativePath;
+ public long DirTimestamp;
+ public string SongHash;
+ public float Duration;
+ public string LevelId;
+ public string InfoDatJson;
+ public string SongDataJson;
+ }
+
+
+ private static ConcurrentDictionary _entries = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
+
+ internal static int Count => _entries.Count;
+
+ internal static void Load()
+ {
+ _entries.Clear();
+
+ if (File.Exists(CachePath))
+ {
+ try
+ {
+ LoadBinary();
+ Plugin.Log.Info($"Loaded binary cache: {_entries.Count} entries from {CachePath}");
+ return;
+ }
+ catch (Exception ex)
+ {
+ Plugin.Log.Warn($"Failed to load binary cache, will rebuild: {ex.Message}");
+ _entries.Clear();
+ }
+ }
+
+
+ LoadLegacyJsonCaches();
+ }
+
+ internal static bool TryGetValid(string relativePath, long currentDirTimestamp, out CacheEntry entry)
+ {
+ if (_entries.TryGetValue(relativePath, out entry) && entry.DirTimestamp == currentDirTimestamp)
+ {
+ return true;
+ }
+
+ entry = null;
+ return false;
+ }
+
+ internal static bool TryGet(string relativePath, out CacheEntry entry)
+ {
+ return _entries.TryGetValue(relativePath, out entry);
+ }
+
+ internal static void Set(string relativePath, CacheEntry entry)
+ {
+ entry.RelativePath = relativePath;
+ _entries[relativePath] = entry;
+ }
+
+ internal static bool Remove(string relativePath)
+ {
+ return _entries.TryRemove(relativePath, out _);
+ }
+
+ internal static void SaveAndPrune(ICollection activePaths)
+ {
+
+ var activeSet = new HashSet(activePaths, StringComparer.OrdinalIgnoreCase);
+ foreach (var key in _entries.Keys)
+ {
+ var absolutePath = Hashing.GetAbsolutePath(key);
+ if (!activeSet.Contains(absolutePath) && !activeSet.Contains(key))
+ {
+ _entries.TryRemove(key, out _);
+ }
+ }
+
+ try
+ {
+ SaveBinary();
+ Plugin.Log.Info($"Saved binary cache: {_entries.Count} entries to {CachePath}");
+ }
+ catch (Exception ex)
+ {
+ Plugin.Log.Error($"Failed to save binary cache: {ex.Message}");
+ Plugin.Log.Error(ex);
+ }
+ }
+
+ internal static IEnumerable> GetAllEntries()
+ {
+ return _entries;
+ }
+
+ #region Binary Format I/O
+
+ private static void LoadBinary()
+ {
+ using var fs = new FileStream(CachePath, FileMode.Open, FileAccess.Read, FileShare.Read, 65536);
+ using var reader = new BinaryReader(fs, Encoding.UTF8, leaveOpen: false);
+
+
+ var magic = reader.ReadString();
+ if (magic != Magic)
+ {
+ throw new InvalidDataException($"Invalid cache magic: expected '{Magic}', got '{magic}'");
+ }
+
+ var version = reader.ReadInt32();
+ if (version != FormatVersion)
+ {
+ throw new InvalidDataException($"Unsupported cache version: {version}");
+ }
+
+ var count = reader.ReadInt32();
+
+ for (int i = 0; i < count; i++)
+ {
+ var entry = new CacheEntry
+ {
+ RelativePath = reader.ReadString(),
+ DirTimestamp = reader.ReadInt64(),
+ SongHash = reader.ReadString(),
+ Duration = reader.ReadSingle(),
+ LevelId = reader.ReadString(),
+ InfoDatJson = reader.ReadString(),
+ SongDataJson = reader.ReadString()
+ };
+
+ _entries[entry.RelativePath] = entry;
+ }
+ }
+
+ private static void SaveBinary()
+ {
+ var tempPath = CachePath + ".tmp";
+ using (var fs = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None, 65536))
+ using (var writer = new BinaryWriter(fs, Encoding.UTF8, leaveOpen: false))
+ {
+ writer.Write(Magic);
+ writer.Write(FormatVersion);
+ writer.Write(_entries.Count);
+
+ foreach (var kvp in _entries)
+ {
+ var entry = kvp.Value;
+ writer.Write(entry.RelativePath ?? string.Empty);
+ writer.Write(entry.DirTimestamp);
+ writer.Write(entry.SongHash ?? string.Empty);
+ writer.Write(entry.Duration);
+ writer.Write(entry.LevelId ?? string.Empty);
+ writer.Write(entry.InfoDatJson ?? string.Empty);
+ writer.Write(entry.SongDataJson ?? string.Empty);
+ }
+ }
+
+ if (File.Exists(CachePath))
+ {
+ File.Delete(CachePath);
+ }
+ File.Move(tempPath, CachePath);
+ }
+
+ #endregion
+
+ #region Legacy JSON Migration
+
+ private static void LoadLegacyJsonCaches()
+ {
+ int migrated = 0;
+
+
+ if (File.Exists(Hashing.cachedHashDataPath))
+ {
+ try
+ {
+ using var reader = new JsonTextReader(new StreamReader(Hashing.cachedHashDataPath));
+ var serializer = JsonSerializer.CreateDefault();
+ var hashData = serializer.Deserialize>(reader);
+ if (hashData != null)
+ {
+ foreach (var kvp in hashData)
+ {
+ var entry = GetOrCreate(kvp.Key);
+ entry.DirTimestamp = kvp.Value.directoryHash;
+ entry.SongHash = kvp.Value.songHash;
+ migrated++;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Plugin.Log.Warn($"Failed to migrate legacy hash cache: {ex.Message}");
+ }
+ }
+
+
+ if (File.Exists(Hashing.cachedAudioDataPath))
+ {
+ try
+ {
+ using var reader = new JsonTextReader(new StreamReader(Hashing.cachedAudioDataPath));
+ var serializer = JsonSerializer.CreateDefault();
+ var audioData = serializer.Deserialize>(reader);
+ if (audioData != null)
+ {
+ foreach (var kvp in audioData)
+ {
+ var entry = GetOrCreate(kvp.Key);
+ entry.Duration = kvp.Value.duration;
+ entry.LevelId = kvp.Value.id;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Plugin.Log.Warn($"Failed to migrate legacy duration cache: {ex.Message}");
+ }
+ }
+
+ if (migrated > 0)
+ {
+ Plugin.Log.Info($"Migrated {migrated} entries from legacy JSON caches to binary format.");
+ }
+ }
+
+ private static CacheEntry GetOrCreate(string relativePath)
+ {
+ return _entries.GetOrAdd(relativePath, _ => new CacheEntry { RelativePath = relativePath });
+ }
+
+ #endregion
+
+ #region Backward Compatibility Helpers
+
+ internal static void PopulateLegacyHashDictionary(ConcurrentDictionary target)
+ {
+ target.Clear();
+ foreach (var kvp in _entries)
+ {
+ if (!string.IsNullOrEmpty(kvp.Value.SongHash))
+ {
+ target[kvp.Key] = new SongHashData(kvp.Value.DirTimestamp, kvp.Value.SongHash);
+ }
+ }
+ }
+
+ internal static void PopulateLegacyAudioDictionary(ConcurrentDictionary target)
+ {
+ target.Clear();
+ foreach (var kvp in _entries)
+ {
+ if (kvp.Value.Duration > 0)
+ {
+ target[kvp.Key] = new AudioCacheData(kvp.Value.LevelId ?? string.Empty, kvp.Value.Duration);
+ }
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/source/SongCore/Utilities/Hashing.cs b/source/SongCore/Utilities/Hashing.cs
index 40e85c3..fe6628b 100644
--- a/source/SongCore/Utilities/Hashing.cs
+++ b/source/SongCore/Utilities/Hashing.cs
@@ -20,23 +20,9 @@ public class Hashing
public static void ReadCachedSongHashes()
{
- if (File.Exists(cachedHashDataPath))
- {
- try
- {
- var songHashData = JsonConvert.DeserializeObject>(File.ReadAllText(cachedHashDataPath));
- if (songHashData != null)
- {
- cachedSongHashData = songHashData;
- Plugin.Log.Info($"Finished loading cached hashes for {cachedSongHashData.Count} songs.");
- }
- }
- catch (Exception ex)
- {
- Plugin.Log.Error($"Error loading cached song hashes: {ex.Message}");
- Plugin.Log.Error(ex);
- }
- }
+ BinaryCache.Load();
+ BinaryCache.PopulateLegacyHashDictionary(cachedSongHashData);
+ Plugin.Log.Info($"Finished loading cached hashes for {cachedSongHashData.Count} songs.");
}
public static void UpdateCachedHashes(HashSet currentSongPaths)
@@ -44,52 +30,33 @@ public static void UpdateCachedHashes(HashSet currentSongPaths)
UpdateCachedHashesInternal(currentSongPaths);
}
- ///
- /// Intended for use in the Loader
- ///
- ///
internal static void UpdateCachedHashesInternal(ICollection currentSongPaths)
{
- foreach (var levelPath in cachedSongHashData.Keys)
+
+ foreach (var kvp in cachedSongHashData)
{
- var absolutePath = GetAbsolutePath(levelPath);
- if (!currentSongPaths.Contains(absolutePath) || (absolutePath == levelPath && IsInInstallPath(levelPath)))
+ if (BinaryCache.TryGet(kvp.Key, out var existing))
{
- cachedSongHashData.TryRemove(levelPath, out _);
+ existing.SongHash = kvp.Value.songHash;
+ existing.DirTimestamp = kvp.Value.directoryHash;
+ }
+ else
+ {
+ BinaryCache.Set(kvp.Key, new BinaryCache.CacheEntry
+ {
+ RelativePath = kvp.Key,
+ DirTimestamp = kvp.Value.directoryHash,
+ SongHash = kvp.Value.songHash
+ });
}
- }
-
- try
- {
- Plugin.Log.Info($"Saving cached hashes for {cachedSongHashData.Count} songs.");
- File.WriteAllText(cachedHashDataPath, JsonConvert.SerializeObject(cachedSongHashData));
- }
- catch (Exception ex)
- {
- Plugin.Log.Error($"Error saving cached song hashes: {ex.Message}");
- Plugin.Log.Error(ex);
}
}
public static void ReadCachedAudioData()
{
- if (File.Exists(cachedAudioDataPath))
- {
- try
- {
- var audioData = JsonConvert.DeserializeObject>(File.ReadAllText(cachedAudioDataPath));
- if (audioData != null)
- {
- cachedAudioData = audioData;
- Plugin.Log.Info($"Finished loading cached durations for {cachedAudioData.Count} songs.");
- }
- }
- catch (Exception ex)
- {
- Plugin.Log.Error($"Error loading cached song durations: {ex.Message}");
- Plugin.Log.Error(ex);
- }
- }
+
+ BinaryCache.PopulateLegacyAudioDictionary(cachedAudioData);
+ Plugin.Log.Info($"Finished loading cached durations for {cachedAudioData.Count} songs.");
}
public static void UpdateCachedAudioData(HashSet currentSongPaths)
@@ -97,31 +64,16 @@ public static void UpdateCachedAudioData(HashSet currentSongPaths)
UpdateCachedAudioDataInternal(currentSongPaths);
}
- ///
- /// Intended for use in the Loader
- ///
- ///
internal static void UpdateCachedAudioDataInternal(ICollection currentSongPaths)
{
- foreach (var levelPath in cachedAudioData.Keys)
+ foreach (var kvp in cachedAudioData)
{
- var absolutePath = GetAbsolutePath(levelPath);
- if (!currentSongPaths.Contains(absolutePath) || (absolutePath == levelPath && IsInInstallPath(levelPath)))
+ if (BinaryCache.TryGet(kvp.Key, out var existing))
{
- cachedAudioData.TryRemove(levelPath, out _);
+ existing.Duration = kvp.Value.duration;
+ existing.LevelId = kvp.Value.id;
}
}
-
- try
- {
- Plugin.Log.Info($"Saving cached durations for {cachedAudioData.Count} songs.");
- File.WriteAllText(cachedAudioDataPath, JsonConvert.SerializeObject(cachedAudioData));
- }
- catch (Exception ex)
- {
- Plugin.Log.Error($"Error saving cached song durations: {ex.Message}");
- Plugin.Log.Error(ex);
- }
}
private static long GetDirectoryHash(string directory)
@@ -144,9 +96,11 @@ private static bool GetCachedSongData(string customLevelPath, out long directory
directoryHash = GetDirectoryHash(customLevelPath);
TryGetRelativePath(customLevelPath, out var relativePath);
- if (cachedSongHashData.TryGetValue(relativePath, out var cachedSong) && cachedSong.directoryHash == directoryHash)
+
+ if (BinaryCache.TryGetValid(relativePath, directoryHash, out var cachedEntry) &&
+ !string.IsNullOrEmpty(cachedEntry.SongHash))
{
- cachedSongHash = cachedSong.songHash;
+ cachedSongHash = cachedEntry.SongHash;
return true;
}
@@ -221,6 +175,14 @@ public static string ComputeCustomLevelHash(CustomLevelFolderInfo customLevelFol
string hash = CreateSha1FromFilesWithPrependBytes(prependBytes, files);
TryGetRelativePath(customLevelFolderInfo.folderPath, out var relativePath);
cachedSongHashData[relativePath] = new SongHashData(directoryHash, hash);
+
+
+ var entry = BinaryCache.TryGet(relativePath, out var existing) ? existing : new BinaryCache.CacheEntry();
+ entry.RelativePath = relativePath;
+ entry.DirTimestamp = directoryHash;
+ entry.SongHash = hash;
+ BinaryCache.Set(relativePath, entry);
+
return hash;
}