Skip to content

Commit

Permalink
Rework score storage to rely on beatmap MD5 rather than beatmapset di…
Browse files Browse the repository at this point in the history
…rectory and beatmap filename
  • Loading branch information
Rian8337 committed Oct 28, 2024
1 parent eb7da1a commit 17cad65
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 221 deletions.
21 changes: 7 additions & 14 deletions schemas/com.reco1l.osu.data.DroidDatabase/1.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "1f4b2f2358a9efc2bfef64bd9d0e3a33",
"identityHash": "4c8cbbe0214619c365058bec4f53da6a",
"entities": [
{
"tableName": "BeatmapInfo",
Expand Down Expand Up @@ -274,7 +274,7 @@
},
{
"tableName": "ScoreInfo",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `beatmapFilename` TEXT NOT NULL, `beatmapSetDirectory` TEXT NOT NULL, `playerName` TEXT NOT NULL, `replayFilename` TEXT NOT NULL, `mods` TEXT NOT NULL, `score` INTEGER NOT NULL, `maxCombo` INTEGER NOT NULL, `mark` TEXT NOT NULL, `hit300k` INTEGER NOT NULL, `hit300` INTEGER NOT NULL, `hit100k` INTEGER NOT NULL, `hit100` INTEGER NOT NULL, `hit50` INTEGER NOT NULL, `misses` INTEGER NOT NULL, `time` INTEGER NOT NULL)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `beatmapMD5` TEXT NOT NULL, `playerName` TEXT NOT NULL, `replayFilename` TEXT NOT NULL, `mods` TEXT NOT NULL, `score` INTEGER NOT NULL, `maxCombo` INTEGER NOT NULL, `mark` TEXT NOT NULL, `hit300k` INTEGER NOT NULL, `hit300` INTEGER NOT NULL, `hit100k` INTEGER NOT NULL, `hit100` INTEGER NOT NULL, `hit50` INTEGER NOT NULL, `misses` INTEGER NOT NULL, `time` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
Expand All @@ -283,14 +283,8 @@
"notNull": true
},
{
"fieldPath": "beatmapFilename",
"columnName": "beatmapFilename",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "beatmapSetDirectory",
"columnName": "beatmapSetDirectory",
"fieldPath": "beatmapMD5",
"columnName": "beatmapMD5",
"affinity": "TEXT",
"notNull": true
},
Expand Down Expand Up @@ -384,11 +378,10 @@
"name": "beatmapIdx",
"unique": false,
"columnNames": [
"beatmapFilename",
"beatmapSetDirectory"
"beatmapMD5"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `beatmapIdx` ON `${TABLE_NAME}` (`beatmapFilename`, `beatmapSetDirectory`)"
"createSql": "CREATE INDEX IF NOT EXISTS `beatmapIdx` ON `${TABLE_NAME}` (`beatmapMD5`)"
}
],
"foreignKeys": []
Expand Down Expand Up @@ -488,7 +481,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1f4b2f2358a9efc2bfef64bd9d0e3a33')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4c8cbbe0214619c365058bec4f53da6a')"
]
}
}
43 changes: 36 additions & 7 deletions src/com/edlplan/replay/OsuDroidReplayPack.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import static com.reco1l.osu.data.Scores.ScoreInfo;

import com.reco1l.osu.data.BeatmapInfo;
import com.reco1l.osu.data.ScoreInfo;

import org.apache.commons.io.FilenameUtils;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
Expand All @@ -19,25 +22,32 @@
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import ru.nsu.ccfit.zuev.osu.scoring.Replay;

public class OsuDroidReplayPack {

public static void packTo(File file, ScoreInfo scoreInfo) throws Exception {
public static void packTo(File file, BeatmapInfo beatmapInfo, ScoreInfo scoreInfo) throws Exception {
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write(pack(scoreInfo));
outputStream.write(pack(beatmapInfo, scoreInfo));
outputStream.close();
}

public static byte[] pack(ScoreInfo scoreInfo) throws Exception {
public static byte[] pack(BeatmapInfo beatmapInfo, ScoreInfo scoreInfo) throws Exception {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream outputStream = new ZipOutputStream(byteArrayOutputStream)){
outputStream.putNextEntry(new ZipEntry("entry.json"));

JSONObject entryJson = new JSONObject();
entryJson.put("version", 1);
entryJson.put("replaydata", scoreInfo.toJSON());
JSONObject replayData = scoreInfo.toJSON();

// ScoreInfo does not contain beatmap info
replayData.put("filename", beatmapInfo.getFullBeatmapsetName() + '/' + beatmapInfo.getFullBeatmapName());

entryJson.put("version", 2);
entryJson.put("replaydata", replayData);

outputStream.write(entryJson.toString(2).getBytes());

Expand Down Expand Up @@ -74,8 +84,27 @@ public static ReplayEntry unpack(InputStream raw) throws IOException, JSONExcept
}
inputStream.close();

entry.scoreInfo = ScoreInfo(new JSONObject(new String(zipEntryMap.get("entry.json"))).getJSONObject("replaydata"));
entry.replayFile = zipEntryMap.get(entry.scoreInfo.getReplayFilename());
var json = new JSONObject(new String(zipEntryMap.get("entry.json")));
int version = json.getInt("version");
var replayData = json.getJSONObject("replaydata");
var replayFilename = FilenameUtils.getName(replayData.getString("replayfile"));
var replayFile = zipEntryMap.get(replayFilename);

if (version < 2) {
// Exported replay v1 does not contain MD5 hash, so we need to obtain it from the odr file.
try (var byteArrayInputStream = new ByteArrayInputStream(replayFile)) {
var replay = new Replay(false);

if (!replay.load(byteArrayInputStream, replayFilename, false)) {
throw new IOException("Failed to load replay");
}

replayData.put("md5", replay.getMd5());
}
}

entry.scoreInfo = ScoreInfo(replayData);
entry.replayFile = replayFile;

return entry;
}
Expand Down
9 changes: 6 additions & 3 deletions src/com/edlplan/ui/fragment/ScoreMenuFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.edlplan.replay.OsuDroidReplayPack;
import com.edlplan.ui.BaseAnimationListener;
import com.edlplan.ui.EasingHelper;
import com.reco1l.osu.data.BeatmapInfo;
import com.reco1l.osu.data.DatabaseManager;
import com.reco1l.osu.ui.MessageDialog;
import com.reco1l.toolkt.android.Dimensions;
Expand All @@ -29,6 +30,7 @@

public class ScoreMenuFragment extends BaseFragment {

private BeatmapInfo beatmap;
private int scoreId;

public ScoreMenuFragment() {
Expand All @@ -48,7 +50,7 @@ protected void onLoadView() {

if (scoreInfo != null) {
try {
String beatmapFilename = scoreInfo.getBeatmapFilename();
String beatmapFilename = beatmap.getFilename();

final File file = new File(
new File(Environment.getExternalStorageDirectory(), "osu!droid/export"),
Expand All @@ -60,7 +62,7 @@ protected void onLoadView() {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
OsuDroidReplayPack.packTo(file, scoreInfo);
OsuDroidReplayPack.packTo(file, beatmap, scoreInfo);

Snackbar.make(v, String.format(getResources().getString(com.edlplan.osudroidresource.R.string.frg_score_menu_export_succeed), file.getAbsolutePath()), 2750).setAction("Share", new View.OnClickListener() {
@Override
Expand Down Expand Up @@ -160,7 +162,8 @@ public void onAnimationEnd(Animator animation) {
}


public void show(int scoreId) {
public void show(BeatmapInfo beatmap, int scoreId) {
this.beatmap = beatmap;
this.scoreId = scoreId;
show();
}
Expand Down
17 changes: 13 additions & 4 deletions src/com/reco1l/osu/data/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ru.nsu.ccfit.zuev.osuplus.BuildConfig
import java.io.File
import java.io.IOException
import java.io.ObjectInputStream
import ru.nsu.ccfit.zuev.osu.scoring.Replay


// Ported from rimu! project
Expand Down Expand Up @@ -72,7 +73,7 @@ object DatabaseManager {
.allowMainThreadQueries()
.build()

loadLegacyMigrations(context)
// loadLegacyMigrations(context)
}

@Suppress("UNCHECKED_CAST")
Expand Down Expand Up @@ -166,14 +167,22 @@ object DatabaseManager {
continue
}

val replayFilePath = it.getString(it.getColumnIndexOrThrow("replayfile"))
val replay = Replay()

if (!replay.load(replayFilePath, false)) {
Log.e("ScoreLibrary", "Failed to import score from old database. Replay file not found.")
pendingScores--
continue
}

val beatmapPath = FilenameUtils.normalizeNoEndSeparator(it.getString(it.getColumnIndexOrThrow("filename")))

scoreInfos += ScoreInfo(
id = id,
beatmapFilename = FilenameUtils.getName(beatmapPath),
beatmapSetDirectory = FilenameUtils.getName(beatmapPath.substringBeforeLast('/')),
beatmapMD5 = replay.md5,
playerName = it.getString(it.getColumnIndexOrThrow("playername")),
replayFilename = FilenameUtils.getName(it.getString(it.getColumnIndexOrThrow("replayfile"))),
replayFilename = FilenameUtils.getName(replayFilePath),
mods = it.getString(it.getColumnIndexOrThrow("mode")),
score = it.getInt(it.getColumnIndexOrThrow("score")),
maxCombo = it.getInt(it.getColumnIndexOrThrow("combo")),
Expand Down
35 changes: 12 additions & 23 deletions src/com/reco1l/osu/data/Scores.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2

@Entity(
indices = [
Index(name = "beatmapIdx", value = ["beatmapFilename", "beatmapSetDirectory"]),
Index(name = "beatmapIdx", value = ["beatmapMD5"]),
]
)
data class ScoreInfo @JvmOverloads constructor(
Expand All @@ -28,14 +28,9 @@ data class ScoreInfo @JvmOverloads constructor(
val id: Long = 0,

/**
* The beatmap filename.
* The MD5 hash of the beatmap.
*/
val beatmapFilename: String,

/**
* The beatmap set directory.
*/
val beatmapSetDirectory: String,
val beatmapMD5: String,

/**
* The player name.
Expand Down Expand Up @@ -127,9 +122,9 @@ data class ScoreInfo @JvmOverloads constructor(

// The keys don't correspond to the table columns in order to keep compatibility with the old replays.
put("id", id)
put("filename", "$beatmapSetDirectory/$beatmapFilename")
put("playername", playerName)
put("replayfile", replayFilename)
put("md5", beatmapMD5)
put("mod", mods)
put("score", score)
put("combo", maxCombo)
Expand All @@ -148,7 +143,7 @@ data class ScoreInfo @JvmOverloads constructor(
fun toStatisticV2() = StatisticV2().also {

it.playerName = playerName
it.setBeatmap(beatmapSetDirectory, beatmapFilename)
it.setBeatmapMD5(beatmapMD5)
it.replayFilename = replayFilename
it.setModFromString(mods)
it.setForcedScore(score)
Expand All @@ -167,14 +162,10 @@ data class ScoreInfo @JvmOverloads constructor(

}

fun ScoreInfo(json: JSONObject): ScoreInfo {

val beatmapPath = FilenameUtils.normalizeNoEndSeparator(json.getString("filename"))
fun ScoreInfo(json: JSONObject) =
ScoreInfo(

return ScoreInfo(

beatmapFilename = FilenameUtils.getName(beatmapPath),
beatmapSetDirectory = FilenameUtils.getName(beatmapPath.substringBeforeLast('/')),
beatmapMD5 = json.getString("md5"),
replayFilename = FilenameUtils.getName(json.getString("replayfile")),

// The keys don't correspond to the table columns in order to keep compatibility with the old replays.
Expand All @@ -192,20 +183,18 @@ fun ScoreInfo(json: JSONObject): ScoreInfo {
misses = json.getInt("misses"),
time = json.getLong("time"),
)
}


@Dao
interface IScoreInfoDAO {

@Query("SELECT * FROM ScoreInfo WHERE beatmapSetDirectory = :beatmapSetDirectory AND beatmapFilename = :beatmapFilename ORDER BY score DESC")
fun getBeatmapScores(beatmapSetDirectory: String, beatmapFilename: String): List<ScoreInfo>
@Query("SELECT * FROM ScoreInfo WHERE beatmapMD5 = :beatmapMD5 ORDER BY score DESC")
fun getBeatmapScores(beatmapMD5: String): List<ScoreInfo>

@Query("SELECT * FROM ScoreInfo WHERE id = :id")
fun getScore(id: Int): ScoreInfo?

@Query("SELECT mark FROM ScoreInfo WHERE beatmapSetDirectory = :beatmapSetDirectory AND beatmapFilename = :beatmapFilename ORDER BY score DESC LIMIT 1")
fun getBestMark(beatmapSetDirectory: String, beatmapFilename: String): String?
@Query("SELECT mark FROM ScoreInfo WHERE beatmapMD5 = :beatmapMD5 ORDER BY score DESC LIMIT 1")
fun getBestMark(beatmapMD5: String): String?

@Insert(onConflict = OnConflictStrategy.ABORT)
fun insertScore(score: ScoreInfo): Long
Expand Down
4 changes: 2 additions & 2 deletions src/com/rian/osu/replay/ReplayImporter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ object ReplayImporter {
private fun importOdr(file: File) {
val replay = Replay()

if (!replay.loadInfo(file.path)) {
if (!replay.load(file.path, false)) {
throw Exception("Failed to load replay info")
}

Expand All @@ -93,7 +93,7 @@ object ReplayImporter {
// For temporary replays, we need to change the extension of the replay to odr.
// While we are at it, rename the replay file into something meaningful and not conflict other replays.
if (replayFilename.startsWith("importedReplay") && replayFilename.endsWith(".tmp")) {
replayFilename = playerName + "_" + beatmapFilename + "_" + System.currentTimeMillis() + ".odr"
replayFilename = playerName + "_" + replay.beatmapsetName + "_" + replay.beatmapName + "_" + System.currentTimeMillis() + ".odr"
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/ru/nsu/ccfit/zuev/osu/MainScene.java
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,7 @@ public void setBeatmap(BeatmapInfo beatmapInfo) {

public void watchReplay(String replayFile) {
Replay replay = new Replay();
if (replay.loadInfo(replayFile)) {
if (replay.load(replayFile, false)) {
if (replay.replayVersion >= 3) {
//replay
ScoringScene scorescene = GlobalManager.getInstance().getScoring();
Expand Down
6 changes: 3 additions & 3 deletions src/ru/nsu/ccfit/zuev/osu/game/GameScene.java
Original file line number Diff line number Diff line change
Expand Up @@ -519,12 +519,12 @@ private boolean loadGame(final BeatmapInfo beatmapInfo, final String rFile) {
offsetRegs = 0;

replaying = false;
replay = new Replay();
replay = new Replay(true);
replay.setObjectCount(objects.size());
replay.setBeatmap(beatmapInfo.getFullBeatmapsetName(), beatmapInfo.getFullBeatmapName(), parsedBeatmap.getMd5());

if (replayFilePath != null) {
replaying = replay.load(replayFilePath);
replaying = replay.load(replayFilePath, true);
if (!replaying) {
ToastLogger.showTextId(com.edlplan.osudroidresource.R.string.replay_invalid, true);
return false;
Expand Down Expand Up @@ -2510,7 +2510,7 @@ public boolean saveFailedReplay() {

if (stat.getTotalScoreWithMultiplier() > 0 && !stat.getMod().contains(GameMod.MOD_AUTO)) {
stat.setReplayFilename(odrFilename);
stat.setBeatmap(lastBeatmapInfo.getSetDirectory(), lastBeatmapInfo.getFilename());
stat.setBeatmapMD5(lastBeatmapInfo.getMD5());

try {
DatabaseManager.getScoreInfoTable().insertScore(stat.toScoreInfo());
Expand Down
2 changes: 1 addition & 1 deletion src/ru/nsu/ccfit/zuev/osu/menu/BeatmapItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public void updateMark() {
if (beatmapInfo == null) {
return;
}
var newmark = DatabaseManager.getScoreInfoTable().getBestMark(beatmapInfo.getSetDirectory(), beatmapInfo.getFilename());
var newmark = DatabaseManager.getScoreInfoTable().getBestMark(beatmapInfo.getMD5());
if (currentMark != null && currentMark.equals(newmark)) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/ru/nsu/ccfit/zuev/osu/menu/ScoreBoard.java
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ private void initFromLocal(BeatmapInfo beatmap) {

@Override
public void run() {
var scores = DatabaseManager.getScoreInfoTable().getBeatmapScores(beatmap.getSetDirectory(), beatmap.getFilename());
var scores = DatabaseManager.getScoreInfoTable().getBeatmapScores(beatmap.getMD5());

if (scores.isEmpty() || !isActive()) {

Expand Down
2 changes: 1 addition & 1 deletion src/ru/nsu/ccfit/zuev/osu/menu/SongMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -1546,7 +1546,7 @@ public void showPropertiesMenu(BeatmapSetItem item) {
}

public void showDeleteScoreMenu(int scoreId) {
(new ScoreMenuFragment()).show(scoreId);
(new ScoreMenuFragment()).show(selectedBeatmap, scoreId);
}

public void select() {
Expand Down
Loading

0 comments on commit 17cad65

Please sign in to comment.