From aaf2073ff745662ad2618a04c43b180459a63271 Mon Sep 17 00:00:00 2001 From: Patryk Gensch <43010113+patryk025@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:40:10 +0200 Subject: [PATCH] Update Rex-EMoolator version --- build.gradle | 2 +- gradle.properties | 2 +- src/main/java/pl/genschu/rexcatalog/Main.java | 9 +-- .../pl/genschu/rexcatalog/db/Database.java | 27 ++++++++ .../genschu/rexcatalog/ingest/Ingestor.java | 62 ++++++++++++------- vendor/Rex-EMoolator | 2 +- 6 files changed, 75 insertions(+), 29 deletions(-) diff --git a/build.gradle b/build.gradle index 8300d4a..98b43e3 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ tasks.register('verifyCoreVersion') { def submodule = file('vendor/Rex-EMoolator') doLast { - if (usingExternal.get()) { + if (usingExternal) { logger.lifecycle('verifyCoreVersion: pominięte (-PemulatorPath, kopia robocza)') return } diff --git a/gradle.properties b/gradle.properties index 6eb05f5..78dbcb6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # Wersja :core, na którą przypięty jest submoduł vendor/Rex-EMoolator. # Zadanie verifyCoreVersion pilnuje, żeby ta wartość zgadzała się z `git describe` # w submodule — inaczej łatwo zbumpować jedno i zapomnieć o drugim. -coreVersion=v0.4.3 +coreVersion=v0.5.1 org.gradle.jvmargs=-Xmx2g org.gradle.parallel=true diff --git a/src/main/java/pl/genschu/rexcatalog/Main.java b/src/main/java/pl/genschu/rexcatalog/Main.java index 28897c0..45c15f0 100644 --- a/src/main/java/pl/genschu/rexcatalog/Main.java +++ b/src/main/java/pl/genschu/rexcatalog/Main.java @@ -101,7 +101,7 @@ public final class Main { try (Database db = Database.open(dataDir); Statement st = db.connection().createStatement(); ResultSet rs = st.executeQuery(""" - SELECT c.display_name, c.container, c.status, c.file_count, + SELECT c.display_name, c.container, c.fs_type, c.status, c.file_count, g.root_path, COALESCE(g.engine_override, g.engine_detected) AS engine, MAX(CASE WHEN d.field = 'edition' THEN d.value END) AS edition, @@ -114,12 +114,13 @@ public final class Main { ORDER BY family, edition, c.display_name, g.root_path """)) { - System.out.printf("%-40s %-10s %-10s %-13s %-22s %s%n", - "KOPIA", "STATUS", "KORZEŃ", "SILNIK", "RODZINA", "WYDANIE"); + System.out.printf("%-40s %-8s %-10s %-10s %-13s %-22s %s%n", + "KOPIA", "FS", "STATUS", "KORZEŃ", "SILNIK", "RODZINA", "WYDANIE"); while (rs.next()) { String root = rs.getString("root_path"); - System.out.printf("%-40s %-10s %-10s %-13s %-22s %s%n", + System.out.printf("%-40s %-8s %-10s %-10s %-13s %-22s %s%n", truncate(rs.getString("display_name"), 40), + nvl(rs.getString("fs_type")), rs.getString("status"), root == null ? "—" : (root.isEmpty() ? "/" : root + "/"), nvl(rs.getString("engine")), diff --git a/src/main/java/pl/genschu/rexcatalog/db/Database.java b/src/main/java/pl/genschu/rexcatalog/db/Database.java index 58cb5d1..c82b177 100644 --- a/src/main/java/pl/genschu/rexcatalog/db/Database.java +++ b/src/main/java/pl/genschu/rexcatalog/db/Database.java @@ -46,6 +46,29 @@ public final class Database implements AutoCloseable { st.executeUpdate(ddl); } } + migrate(); + } + + /** + * Dokłada kolumny, których {@code CREATE TABLE IF NOT EXISTS} nie doda do już + * istniejącej tabeli. Tylko {@code ADD COLUMN} — bez przebudowy tabel, bo na + * {@code copy} wiszą klucze obce z {@code ON DELETE CASCADE}. + */ + private void migrate() throws SQLException { + addColumnIfMissing("copy", "fs_type", "TEXT"); + } + + private void addColumnIfMissing(String table, String column, String type) throws SQLException { + try (Statement st = connection.createStatement()) { + try (java.sql.ResultSet rs = st.executeQuery("PRAGMA table_info(" + table + ")")) { + while (rs.next()) { + if (column.equalsIgnoreCase(rs.getString("name"))) { + return; + } + } + } + st.executeUpdate("ALTER TABLE " + table + " ADD COLUMN " + column + " " + type); + } } @Override @@ -98,6 +121,9 @@ public final class Database implements AutoCloseable { // Kopia to nośnik, nie gra: jedna płyta bywa dwupackiem z dwoma drzewami // gier, więc przypisanie do wydania wisi przy game_root, nie tutaj. // status mówi, czy zawartość w ogóle dało się odczytać. + // container to rodzaj artefaktu (plik .iso / .zip / katalog na dysku), + // fs_type to system plików wykryty przez :core po zawartości — to nie to samo: + // Wojna Trojańska.iso jest plikiem .iso, ale w środku ma UDF, nie ISO9660. """ CREATE TABLE IF NOT EXISTS copy ( id INTEGER PRIMARY KEY, @@ -105,6 +131,7 @@ public final class Database implements AutoCloseable { status TEXT NOT NULL DEFAULT 'ok' CHECK (status IN ('ok', 'installer', 'unreadable')), status_note TEXT, + fs_type TEXT, path TEXT NOT NULL UNIQUE, display_name TEXT, size INTEGER, diff --git a/src/main/java/pl/genschu/rexcatalog/ingest/Ingestor.java b/src/main/java/pl/genschu/rexcatalog/ingest/Ingestor.java index a6dabf9..ef93cb4 100644 --- a/src/main/java/pl/genschu/rexcatalog/ingest/Ingestor.java +++ b/src/main/java/pl/genschu/rexcatalog/ingest/Ingestor.java @@ -1,6 +1,8 @@ package pl.genschu.rexcatalog.ingest; import pl.genschu.bloomooemulator.engine.filesystem.AssetSourceDispatcher; +import pl.genschu.bloomooemulator.engine.filesystem.FileSystemDetector; +import pl.genschu.bloomooemulator.engine.filesystem.FileSystemType; import pl.genschu.bloomooemulator.engine.filesystem.IFileSystem; import pl.genschu.rexcatalog.db.Database; @@ -25,7 +27,8 @@ import java.util.Map; * Indeksuje pojedynczą kopię (ISO / ZIP / rozpakowany katalog) do bazy katalogu. * *

Nic nie rozpakowuje na dysk — czyta przez {@link IFileSystem} z {@code :core}, - * czyli tę samą implementację ISO9660/Joliet, której używa emulator przy uruchamianiu gry. + * czyli te same implementacje ISO9660/Joliet i UDF, których używa emulator + * przy uruchamianiu gry. * Kopia, której nie da się odczytać, i tak trafia do bazy — ze statusem i powodem, * bo „mam ten obraz, ale go nie otwieram" to też fakt wart skatalogowania. */ @@ -54,14 +57,16 @@ public final class Ingestor { Map sizeByPath = new LinkedHashMap<>(); Map formatByPath = new LinkedHashMap<>(); long totalBytes; + String fsType = null; try { + // detekcja po zawartości, nie po rozszerzeniu — .iso bywa obrazem UDF + fsType = fsTypeName(FileSystemDetector.detectFileSystemType(source)); IFileSystem fs = AssetSourceDispatcher.openAssets(source); totalBytes = walk(fs, "", sha1ByPath, sizeByPath, formatByPath); } catch (Exception e) { - // np. Wojna Trojańska.iso — obraz UDF, którego IsoFileSystem nie parsuje String note = e.getMessage() == null ? e.toString() : e.getMessage(); - persistUnreadable(source, container, containerSha256, note); + persistUnreadable(source, container, fsType, containerSha256, note); return new Result(source.getPath(), container, "unreadable", note, 0, 0, List.of()); } @@ -74,7 +79,7 @@ public final class Ingestor { } long containerSize = source.isDirectory() ? totalBytes : source.length(); - persist(source, container, status, statusNote, containerSize, containerSha256, + persist(source, container, fsType, status, statusNote, containerSize, containerSha256, sha1ByPath, sizeByPath, formatByPath, roots); List summaries = new ArrayList<>(); @@ -93,6 +98,16 @@ public final class Ingestor { .orElse(null); } + private static String fsTypeName(FileSystemType type) { + return switch (type) { + case DIRECTORY -> "dir"; + case ZIP -> "zip"; + case ISO9660 -> "iso9660"; + case UDF -> "udf"; + case UNKNOWN -> null; + }; + } + private static String containerType(File source) throws IOException { if (source.isDirectory()) { return "dir"; @@ -154,13 +169,13 @@ public final class Ingestor { return bytes; } - private void persistUnreadable(File source, String container, String sha256, String note) - throws Exception { + private void persistUnreadable(File source, String container, String fsType, + String sha256, String note) throws Exception { Connection conn = db.connection(); boolean auto = conn.getAutoCommit(); conn.setAutoCommit(false); try { - upsertCopy(conn, source, container, "unreadable", note, + upsertCopy(conn, source, container, fsType, "unreadable", note, source.length(), sha256, 0); conn.commit(); } catch (Exception e) { @@ -171,7 +186,8 @@ public final class Ingestor { } } - private void persist(File source, String container, String status, String statusNote, + private void persist(File source, String container, String fsType, + String status, String statusNote, long size, String sha256, Map sha1ByPath, Map sizeByPath, @@ -181,7 +197,7 @@ public final class Ingestor { boolean previousAutoCommit = conn.getAutoCommit(); conn.setAutoCommit(false); try { - long copyId = upsertCopy(conn, source, container, status, statusNote, + long copyId = upsertCopy(conn, source, container, fsType, status, statusNote, size, sha256, sha1ByPath.size()); try (Statement st = conn.createStatement()) { @@ -261,30 +277,32 @@ public final class Ingestor { } } - private long upsertCopy(Connection conn, File source, String container, + private long upsertCopy(Connection conn, File source, String container, String fsType, String status, String statusNote, long size, String sha256, int fileCount) throws Exception { String path = source.getAbsolutePath(); try (PreparedStatement st = conn.prepareStatement(""" - INSERT INTO copy (container, status, status_note, path, display_name, + INSERT INTO copy (container, fs_type, status, status_note, path, display_name, size, sha256, file_count, ingested_at, core_version) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(path) DO UPDATE SET - container = excluded.container, status = excluded.status, + container = excluded.container, fs_type = excluded.fs_type, + status = excluded.status, status_note = excluded.status_note, size = excluded.size, sha256 = excluded.sha256, file_count = excluded.file_count, ingested_at = excluded.ingested_at, core_version = excluded.core_version """)) { st.setString(1, container); - st.setString(2, status); - st.setString(3, statusNote); - st.setString(4, path); - st.setString(5, source.getName()); - st.setLong(6, size); - st.setString(7, sha256); - st.setInt(8, fileCount); - st.setString(9, Instant.now().toString()); - st.setString(10, coreVersion); + st.setString(2, fsType); + st.setString(3, status); + st.setString(4, statusNote); + st.setString(5, path); + st.setString(6, source.getName()); + st.setLong(7, size); + st.setString(8, sha256); + st.setInt(9, fileCount); + st.setString(10, Instant.now().toString()); + st.setString(11, coreVersion); st.executeUpdate(); } try (PreparedStatement st = conn.prepareStatement("SELECT id FROM copy WHERE path = ?")) { diff --git a/vendor/Rex-EMoolator b/vendor/Rex-EMoolator index 8140276..23f3f65 160000 --- a/vendor/Rex-EMoolator +++ b/vendor/Rex-EMoolator @@ -1 +1 @@ -Subproject commit 814027658045b09f32fcd7b2f37c3c8efbbea4cf +Subproject commit 23f3f651001f30f71ca52ab7267edb2059aaacfa