diff --git a/prefabs/map_info_container.tscn b/prefabs/map_info_container.tscn
index 98ccc0ce..7dc3c47d 100644
--- a/prefabs/map_info_container.tscn
+++ b/prefabs/map_info_container.tscn
@@ -6,6 +6,7 @@
[ext_resource type="Theme" uid="uid://dhuhu3mmaew1a" path="res://themes/theme.tres" id="4_4d08m"]
[ext_resource type="Script" uid="uid://c1235cyjfh4rl" path="res://scripts/ui/LinkPopupButton.cs" id="4_dccrs"]
[ext_resource type="Texture2D" uid="uid://7px5v8ikildx" path="res://user/skins/default/ui/buttons/favorite.png" id="5_1m5pv"]
+[ext_resource type="Texture2D" uid="uid://dkpbociv8fgfd" path="res://user/skins/default/ui/buttons/export.png" id="5_g02bu"]
[ext_resource type="Script" uid="uid://dy45uerqy0yd7" path="res://scripts/ui/FlatPreview.cs" id="5_svokg"]
[ext_resource type="Texture2D" uid="uid://degvlg4p0if70" path="res://user/skins/default/ui/buttons/add_video.png" id="6_g02bu"]
[ext_resource type="Texture2D" uid="uid://cgxbwx4f28lt2" path="res://user/skins/default/ui/buttons/copy.png" id="7_oibm8"]
@@ -175,7 +176,7 @@ outline_color = Color(0, 0, 0, 1)
shadow_size = 4
shadow_color = Color(0, 0, 0, 1)
-[node name="MapInfoContainer" type="Panel" unique_id=150923669 node_paths=PackedStringArray("info", "dim", "coverBackground", "cover", "infoSubholder", "mainLabel", "extraLabel", "artistLink", "favoriteButton", "videoButton", "videoDialog", "copyButton", "copyDialog", "deleteButton", "actions", "preview", "speedHolder", "speedPresets", "playHolder", "startButton", "leaderboard", "lbScrollContainer", "lbContainer", "lbExpand", "lbHide")]
+[node name="MapInfoContainer" type="Panel" unique_id=150923669 node_paths=PackedStringArray("info", "dim", "coverBackground", "cover", "infoSubholder", "mainLabel", "extraLabel", "artistLink", "exportButton", "favoriteButton", "videoButton", "videoDialog", "copyButton", "copyDialog", "deleteButton", "actions", "preview", "speedHolder", "speedPresets", "playHolder", "startButton", "leaderboard", "lbScrollContainer", "lbContainer", "lbExpand", "lbHide")]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
@@ -193,6 +194,7 @@ infoSubholder = NodePath("Info/ScrollContainer/HBoxContainer")
mainLabel = NodePath("Info/ScrollContainer/HBoxContainer/Subholder/Main")
extraLabel = NodePath("Info/ScrollContainer/HBoxContainer/Subholder/Extra")
artistLink = NodePath("Info/ScrollContainer/HBoxContainer/Background/ArtistLink")
+exportButton = NodePath("Info/ScrollContainer/HBoxContainer/Subholder/Buttons/Export")
favoriteButton = NodePath("Info/ScrollContainer/HBoxContainer/Subholder/Buttons/Favorite")
videoButton = NodePath("Info/ScrollContainer/HBoxContainer/Subholder/Buttons/Video")
videoDialog = NodePath("Info/ScrollContainer/HBoxContainer/Subholder/Buttons/Video/VideoDialog")
@@ -357,6 +359,17 @@ grow_horizontal = 0
grow_vertical = 0
alignment = 2
+[node name="Export" type="Button" parent="Info/ScrollContainer/HBoxContainer/Subholder/Buttons" unique_id=2033571562]
+layout_mode = 2
+tooltip_text = "Export (Shift+Click for .sspm export)"
+focus_mode = 0
+mouse_default_cursor_shape = 2
+theme = ExtResource("4_4d08m")
+theme_override_colors/font_color = Color(1, 1, 1, 1)
+theme_override_constants/icon_max_width = 24
+icon = ExtResource("5_g02bu")
+icon_alignment = 1
+
[node name="Favorite" type="Button" parent="Info/ScrollContainer/HBoxContainer/Subholder/Buttons" unique_id=1022164792]
layout_mode = 2
tooltip_text = "Favorite"
@@ -390,6 +403,7 @@ filters = PackedStringArray("*.mp4")
use_native_dialog = true
[node name="Copy" type="Button" parent="Info/ScrollContainer/HBoxContainer/Subholder/Buttons" unique_id=2053076461]
+visible = false
layout_mode = 2
tooltip_text = "Copy"
focus_mode = 0
diff --git a/scripts/SoundManager.cs b/scripts/SoundManager.cs
index 9eb59133..f0ecf4eb 100644
--- a/scripts/SoundManager.cs
+++ b/scripts/SoundManager.cs
@@ -221,7 +221,7 @@ public static void PlayJukebox(Map map, bool setRichPresence = true)
JukeboxIndex = MapManager.Maps.FindIndex(x => x.Id == map.Id);
- Song.Stream = Util.Audio.LoadFromFile($"{MapUtil.MapsCacheFolder}/{map.Name}/audio.{map.AudioExt}");
+ Song.Stream = Util.Audio.LoadFromFile($"{MapUtil.MapsFolder}/{map.Name}/audio.{map.AudioExt}");
Song.Play();
Instance.JukeboxPlayed?.Invoke(map);
@@ -280,7 +280,7 @@ public static void StartMapSelectionPlayback(Map map, bool setRichPresence = tru
}
}
- Song.Stream = Util.Audio.LoadFromFile($"{MapUtil.MapsCacheFolder}/{map.Name}/audio.{map.AudioExt}");
+ Song.Stream = Util.Audio.LoadFromFile($"{MapUtil.MapsFolder}/{map.Name}/audio.{map.AudioExt}");
Song.Play(0);
Instance.JukeboxPlayed?.Invoke(map);
diff --git a/scripts/game/managers/ReplayManager.cs b/scripts/game/managers/ReplayManager.cs
index 42166b59..ae3d0269 100644
--- a/scripts/game/managers/ReplayManager.cs
+++ b/scripts/game/managers/ReplayManager.cs
@@ -1,4 +1,7 @@
using System;
+using System.IO;
+
+// using System.IO;
using System.Linq;
using System.Security.Cryptography;
using Godot;
@@ -28,7 +31,7 @@ public enum Mode
public string ReplayPath;
public Vector2 CursorPosition { get; private set; }
- private FileAccess file;
+ private Godot.FileAccess file;
private ulong statusOffset, frameCountOffset;
public void NewReplay(Attempt attempt)
@@ -39,7 +42,7 @@ public void NewReplay(Attempt attempt)
ReplayPath = $"{Constants.USER_FOLDER}/replays/{attempt.ID}.phxr";
- file = FileAccess.Open(ReplayPath, FileAccess.ModeFlags.Write);
+ file = Godot.FileAccess.Open(ReplayPath, Godot.FileAccess.ModeFlags.Write);
file.StoreString("phxr"); // sig
file.Store8(1); // replay file version
@@ -69,7 +72,8 @@ public void NewReplay(Attempt attempt)
}
string serializedMods = string.Join("_", mods);
- string mapName = attempt.Map.FilePath.GetFile().GetBaseName();
+ // string mapName = attempt.Map.FilePath.GetFile().GetBaseName();
+ string mapName = Path.GetFileName(attempt.Map.FolderPath);
string player = "You";
void storeSizedString(string data)
@@ -126,7 +130,7 @@ public void SaveReplay(Attempt attempt)
file.Close();
// open replay to store hash
- file = FileAccess.Open($"{Constants.USER_FOLDER}/replays/{attempt.ID}.phxr", FileAccess.ModeFlags.ReadWrite);
+ file = Godot.FileAccess.Open($"{Constants.USER_FOLDER}/replays/{attempt.ID}.phxr", Godot.FileAccess.ModeFlags.ReadWrite);
ulong length = file.GetLength();
byte[] hash = SHA256.HashData(file.GetBuffer((long)length));
file.StoreBuffer(hash);
diff --git a/scripts/game/ui/PlaytestOverlay.cs b/scripts/game/ui/PlaytestOverlay.cs
index b6917758..1b6102dc 100644
--- a/scripts/game/ui/PlaytestOverlay.cs
+++ b/scripts/game/ui/PlaytestOverlay.cs
@@ -74,7 +74,7 @@ public void UpdatePlaytestOverlay(bool show)
speedValue = speedDouble;
}
- var map = MapParser.Decode(oldAttempt.Map.FilePath, Rhythia.AudioFilePath);
+ var map = MapParser.Decode(oldAttempt.Map.FolderPath, Rhythia.AudioFilePath);
Game.Attempt = new(map, speedValue, GetStartFrom(startFromEdit) * 1000, Rhythia.TempCam, Rhythia.TempMods);
SceneManager.ReloadCurrentScene();
diff --git a/scripts/map/Map.cs b/scripts/map/Map.cs
index 15c8c76f..afbb3638 100644
--- a/scripts/map/Map.cs
+++ b/scripts/map/Map.cs
@@ -17,15 +17,20 @@ public partial class Map : RefCounted
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
+ public string LastModifiedMetadata { get; set; }
+ public string LastModifiedNotes { get; set; }
- public string Hash { get; set; }
+ ///
+ /// The hash of the metadata.json, then the objects.phxmo in order
+ ///
+ public string MetadataObjectHash { get; set; }
public string Collection { get; set; } = string.Empty;
[Ignore]
public MapSet MapSet { get; set; }
- public string FilePath { get; set; } = string.Empty;
+ public string FolderPath { get; set; } = string.Empty;
public bool Favorite { get; set; }
@@ -86,7 +91,7 @@ public Note[] TryParseNotes()
{
try
{
- notes = MapParser.DecodePHXMO($"{MapUtil.MapsCacheFolder}/{Name}/objects.phxmo");
+ notes = MapParser.DecodePHXMO($"{MapUtil.MapsFolder}/{Name}/objects.phxmo");
return notes;
}
catch
@@ -97,7 +102,7 @@ public Note[] TryParseNotes()
private Texture2D getCover()
{
- string path = $"{MapUtil.MapsCacheFolder}/{Name}";
+ string path = $"{MapUtil.MapsFolder}/{Name}";
if (cover == DefaultCover && File.Exists($"{path}/cover.png"))
{
@@ -115,10 +120,13 @@ private Texture2D getCover()
public Map() { }
- public Map(string filePath, Note[] data = null, string id = null, string artist = "", string title = "", float rating = 0, string[] mappers = null, int difficulty = 0, string difficultyName = null, int? length = null, byte[] audioBuffer = null, byte[] coverBuffer = null, byte[] videoBuffer = null, bool ephemeral = false, string artistLink = "", string artistPlatform = "")
+ public Map(string folderPath, string metadataObjHash = "", string lastModifiedMetadata = "", string lastModifiedNotes = "", Note[] data = null, string id = null, string artist = "", string title = "", float rating = 0, string[] mappers = null, int difficulty = 0, string difficultyName = null, int? length = null, byte[] audioBuffer = null, byte[] coverBuffer = null, byte[] videoBuffer = null, bool ephemeral = false, string artistLink = "", string artistPlatform = "")
{
- FilePath = filePath;
+ FolderPath = folderPath;
Ephemeral = ephemeral;
+ MetadataObjectHash = metadataObjHash;
+ LastModifiedMetadata = lastModifiedMetadata;
+ LastModifiedNotes = lastModifiedNotes;
Artist = (artist ?? "").StripEscapes();
ArtistLink = artistLink;
ArtistPlatform = artistPlatform;
diff --git a/scripts/map/MapCache.cs b/scripts/map/MapCache.cs
index ac1d6929..cba136b7 100644
--- a/scripts/map/MapCache.cs
+++ b/scripts/map/MapCache.cs
@@ -1,11 +1,14 @@
-using System;
+using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Godot;
+using Octokit;
using Util;
public static class MapCache
@@ -28,12 +31,28 @@ public static void Load(bool fullSync)
try
{
- string[] files = Directory.GetFiles(MapUtil.MapsFolder, $"*.{Constants.DEFAULT_MAP_EXT}", SearchOption.AllDirectories);
+ // string[] files = Directory.GetFiles(MapUtil.MapsFolder, $"*.{Constants.DEFAULT_MAP_EXT}", SearchOption.AllDirectories);
+
+ // List mapsList = Directory
+ // .GetFiles(MapUtil.MapsFolder, $"*.{Constants.DEFAULT_MAP_EXT}", SearchOption.AllDirectories)
+ // .Concat(Directory.GetDirectories(MapUtil.MapsFolder, "*", SearchOption.AllDirectories))
+ // .ToList();
+
+ // Map files go first since they will be encoded to folders after they get parsed in MapParser.cs
+ List mapsList = Directory.GetFiles(MapUtil.MapsFolder,$"*.{Constants.DEFAULT_MAP_EXT}", SearchOption.AllDirectories)
+ .Concat(Directory.GetDirectories(MapUtil.MapsFolder, "*", SearchOption.AllDirectories))
+ .ToList();
+
+ // List mapsList = Directory.GetDirectories(MapUtil.MapsFolder, "*", SearchOption.AllDirectories)
+ // .Concat(Directory.GetFiles(MapUtil.MapsFolder,$"*.{Constants.DEFAULT_MAP_EXT}", SearchOption.AllDirectories))
+ // .ToList();
+
+ string[] toParseMaps = mapsList.ToArray();
if (fullSync)
{
- syncFiles(files);
- addNonCachedFiles(files);
+ syncFiles(toParseMaps);
+ addNonCachedFiles(toParseMaps);
OnFilesSyncFinished?.Invoke(FilesSynced.Value);
FilesToSync.Value = 0;
@@ -48,36 +67,52 @@ public static void Load(bool fullSync)
}
}
- private static void syncFiles(string[] files)
+ private static void syncFiles(string[] toParseMaps)
{
var maps = FetchAll();
FilesToSync.Value = maps.Count;
FilesSynced.Value = 0;
- for (int i = 0; i < files.Length; i++)
+ for (int i = 0; i < toParseMaps.Length; i++)
{
- files[i] = BackSlashToForwardSlash(files[i]);
+ toParseMaps[i] = BackSlashToForwardSlash(toParseMaps[i]);
}
- var filesHashSet = files.ToHashSet();
+ var mapsHashSet = toParseMaps.ToHashSet();
foreach (var map in maps)
{
- string filePath = BackSlashToForwardSlash(map.FilePath);
- if (filesHashSet.Contains(filePath))
+ string mapPath = BackSlashToForwardSlash(map.FolderPath);
+
+ // Checks if the map is actually inside the maps folder
+ if (mapsHashSet.Contains(mapPath))
{
- string checksum = GetMd5Checksum(filePath);
+ DateTime metadataModifiedDate = File.GetLastWriteTime(Path.Combine(mapPath, "metadata.json"));
+ DateTime objectModifiedDate = File.GetLastWriteTime(Path.Combine(mapPath, "objects.phxmo"));
+
+ // i have to convert to string since sqlite doesnt support DateTime c# conversion
+ // PLUS, theres like seconds or something and it doesnt catch that soooooo
+ string metadataResult = metadataModifiedDate.ToString();
+ string notesResult = objectModifiedDate.ToString();
+
+ bool metadataCheck = map.LastModifiedMetadata == metadataResult;
+ bool objectsCheck = map.LastModifiedNotes == notesResult;
- if (map.Hash == checksum)
+ // last modified is faster and lowkey more important -fog
+ if (metadataCheck || objectsCheck)
{
- if (!Directory.Exists($"{MapUtil.MapsCacheFolder}/{map.Name}"))
- {
- InsertIntoMapCacheFolder(map);
- }
+ FilesSynced.Value++;
+ continue;
+ }
- FilesSynced.Value += 1;
+ // we will do checksum checking after if last modified dates dont match
+ // this code shouldn't be reached unless the dates dont match
+ string checksum = GetMd5Checksum(mapPath);
+ if (map.MetadataObjectHash == checksum)
+ {
+ FilesSynced.Value++;
continue;
}
@@ -85,103 +120,93 @@ private static void syncFiles(string[] files)
try
{
- newMap = MapParser.Decode(filePath, null, false, true);
+ newMap = MapParser.Decode(mapPath, null, false, true);
}
catch (Exception ex)
{
Logger.Error(ex);
- File.Delete(filePath);
+ if (File.Exists(mapPath))
+ {
+ File.Delete(mapPath);
+ }
+ else if (Directory.Exists(mapPath))
+ {
+ Directory.Delete(mapPath, true);
+ }
DatabaseService.Connection.Delete(map);
- FilesSynced.Value += 1;
+ FilesSynced.Value++;
continue;
}
+ newMap.LastModifiedMetadata = metadataModifiedDate.ToString();
+ newMap.LastModifiedNotes = objectModifiedDate.ToString();
+
newMap.Id = map.Id;
- newMap.Hash = checksum;
+ newMap.MetadataObjectHash = checksum;
DatabaseService.Connection.Update(newMap);
- InsertIntoMapCacheFolder(map);
+ // InsertIntoMapCacheFolder(map);
Logger.Log($"Updated cached map: {newMap.Name}");
-
- FilesSynced.Value += 1;
+ FilesSynced.Value++;
continue;
}
else
{
- removeCacheFolder(map);
+ if (Directory.Exists($"{MapUtil.MapsFolder}/{map.Name}"))
+ {
+ Directory.Delete($"{MapUtil.MapsFolder}/{map.Name}", true);
+ }
+
DatabaseService.Connection.Delete(map);
- Logger.Log($"Removed {filePath} from the cache, as it no longer exists.");
+ Logger.Log($"Removed {mapPath} from the cache, as it no longer exists.");
- FilesSynced.Value += 1;
+ FilesSynced.Value++;
}
}
}
- public static void InsertIntoMapCacheFolder(Map map)
- {
- string path = $"{MapUtil.MapsCacheFolder}/{map.Name}";
- using var stream = File.OpenRead(map.FilePath);
- var archive = new ZipArchive(stream);
-
- if (Directory.Exists(path))
- {
- Directory.Delete(path, true);
- }
-
- archive.ExtractToDirectory(path, true);
- }
-
- private static void removeCacheFolder(Map map)
- {
- try
- {
- Directory.Delete($"{MapUtil.MapsCacheFolder}/{map.Name}", true);
- }
- catch
- {
- return;
- }
- }
-
- private static void addNonCachedFiles(string[] files)
+ private static void addNonCachedFiles(string[] toParseMaps)
{
var maps = FetchAll();
HashSet hashSet = new();
- maps.ForEach(map => hashSet.Add(map.FilePath));
+ maps.ForEach(map => hashSet.Add(map.FolderPath));
- foreach (string file in files)
+ // Maps that need to be parsed - maps in database cache
+ FilesToSync.Value = toParseMaps.Count() - maps.Count();
+ FilesSynced.Value = 0;
+
+ foreach (string toParseMap in toParseMaps)
{
- if (hashSet.Contains(BackSlashToForwardSlash(file)))
+ if (hashSet.Contains(BackSlashToForwardSlash(toParseMap)))
{
continue;
}
- FilesToSync.Value += 1;
+ // FilesToSync.Value += 1;
try
{
- var map = MapParser.Decode(file);
- map.Collection = file.GetBaseDir().Split("/")[^1];
- map.FilePath = $"{Constants.USER_FOLDER}/maps/{map.Collection}/{map.Name}.{Constants.DEFAULT_MAP_EXT}";
- map.Hash = GetMd5Checksum(file);
- File.Move(file, map.FilePath);
+ GD.Print($"decoding: {toParseMap}\nfilestosync: {FilesToSync.Value}, filessynced: {FilesSynced.Value}");
+ var map = MapParser.Decode(toParseMap);
+ map.FolderPath = $"{Constants.USER_FOLDER}/maps/{map.Name}";
+ map.MetadataObjectHash = GetMd5Checksum(map.FolderPath);
InsertMap(map);
}
catch
{
- File.Delete(file);
+ Directory.Delete(toParseMap, true);
Logger.Log($"Failed to add map non-cached map");
}
- FilesSynced.Value += 1;
+ FilesSynced.Value++;
}
}
public static int InsertMap(Map map)
{
- var existing = DatabaseService.Connection.Find