Update Rex-EMoolator version
This commit is contained in:
@@ -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")),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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<String, Long> sizeByPath = new LinkedHashMap<>();
|
||||
Map<String, FormatSniffer.Result> 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<RootSummary> 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<String, String> sha1ByPath,
|
||||
Map<String, Long> 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 = ?")) {
|
||||
|
||||
Reference in New Issue
Block a user