Update Rex-EMoolator version
This commit is contained in:
+1
-1
@@ -59,7 +59,7 @@ tasks.register('verifyCoreVersion') {
|
|||||||
def submodule = file('vendor/Rex-EMoolator')
|
def submodule = file('vendor/Rex-EMoolator')
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
if (usingExternal.get()) {
|
if (usingExternal) {
|
||||||
logger.lifecycle('verifyCoreVersion: pominięte (-PemulatorPath, kopia robocza)')
|
logger.lifecycle('verifyCoreVersion: pominięte (-PemulatorPath, kopia robocza)')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# Wersja :core, na którą przypięty jest submoduł vendor/Rex-EMoolator.
|
# Wersja :core, na którą przypięty jest submoduł vendor/Rex-EMoolator.
|
||||||
# Zadanie verifyCoreVersion pilnuje, żeby ta wartość zgadzała się z `git describe`
|
# Zadanie verifyCoreVersion pilnuje, żeby ta wartość zgadzała się z `git describe`
|
||||||
# w submodule — inaczej łatwo zbumpować jedno i zapomnieć o drugim.
|
# w submodule — inaczej łatwo zbumpować jedno i zapomnieć o drugim.
|
||||||
coreVersion=v0.4.3
|
coreVersion=v0.5.1
|
||||||
|
|
||||||
org.gradle.jvmargs=-Xmx2g
|
org.gradle.jvmargs=-Xmx2g
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public final class Main {
|
|||||||
try (Database db = Database.open(dataDir);
|
try (Database db = Database.open(dataDir);
|
||||||
Statement st = db.connection().createStatement();
|
Statement st = db.connection().createStatement();
|
||||||
ResultSet rs = st.executeQuery("""
|
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,
|
g.root_path,
|
||||||
COALESCE(g.engine_override, g.engine_detected) AS engine,
|
COALESCE(g.engine_override, g.engine_detected) AS engine,
|
||||||
MAX(CASE WHEN d.field = 'edition' THEN d.value END) AS edition,
|
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
|
ORDER BY family, edition, c.display_name, g.root_path
|
||||||
""")) {
|
""")) {
|
||||||
|
|
||||||
System.out.printf("%-40s %-10s %-10s %-13s %-22s %s%n",
|
System.out.printf("%-40s %-8s %-10s %-10s %-13s %-22s %s%n",
|
||||||
"KOPIA", "STATUS", "KORZEŃ", "SILNIK", "RODZINA", "WYDANIE");
|
"KOPIA", "FS", "STATUS", "KORZEŃ", "SILNIK", "RODZINA", "WYDANIE");
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
String root = rs.getString("root_path");
|
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),
|
truncate(rs.getString("display_name"), 40),
|
||||||
|
nvl(rs.getString("fs_type")),
|
||||||
rs.getString("status"),
|
rs.getString("status"),
|
||||||
root == null ? "—" : (root.isEmpty() ? "/" : root + "/"),
|
root == null ? "—" : (root.isEmpty() ? "/" : root + "/"),
|
||||||
nvl(rs.getString("engine")),
|
nvl(rs.getString("engine")),
|
||||||
|
|||||||
@@ -46,6 +46,29 @@ public final class Database implements AutoCloseable {
|
|||||||
st.executeUpdate(ddl);
|
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
|
@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
|
// 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.
|
// gier, więc przypisanie do wydania wisi przy game_root, nie tutaj.
|
||||||
// status mówi, czy zawartość w ogóle dało się odczytać.
|
// 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 (
|
CREATE TABLE IF NOT EXISTS copy (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
@@ -105,6 +131,7 @@ public final class Database implements AutoCloseable {
|
|||||||
status TEXT NOT NULL DEFAULT 'ok'
|
status TEXT NOT NULL DEFAULT 'ok'
|
||||||
CHECK (status IN ('ok', 'installer', 'unreadable')),
|
CHECK (status IN ('ok', 'installer', 'unreadable')),
|
||||||
status_note TEXT,
|
status_note TEXT,
|
||||||
|
fs_type TEXT,
|
||||||
path TEXT NOT NULL UNIQUE,
|
path TEXT NOT NULL UNIQUE,
|
||||||
display_name TEXT,
|
display_name TEXT,
|
||||||
size INTEGER,
|
size INTEGER,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package pl.genschu.rexcatalog.ingest;
|
package pl.genschu.rexcatalog.ingest;
|
||||||
|
|
||||||
import pl.genschu.bloomooemulator.engine.filesystem.AssetSourceDispatcher;
|
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.bloomooemulator.engine.filesystem.IFileSystem;
|
||||||
import pl.genschu.rexcatalog.db.Database;
|
import pl.genschu.rexcatalog.db.Database;
|
||||||
|
|
||||||
@@ -25,7 +27,8 @@ import java.util.Map;
|
|||||||
* Indeksuje pojedynczą kopię (ISO / ZIP / rozpakowany katalog) do bazy katalogu.
|
* Indeksuje pojedynczą kopię (ISO / ZIP / rozpakowany katalog) do bazy katalogu.
|
||||||
*
|
*
|
||||||
* <p>Nic nie rozpakowuje na dysk — czyta przez {@link IFileSystem} z {@code :core},
|
* <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,
|
* 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.
|
* 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, Long> sizeByPath = new LinkedHashMap<>();
|
||||||
Map<String, FormatSniffer.Result> formatByPath = new LinkedHashMap<>();
|
Map<String, FormatSniffer.Result> formatByPath = new LinkedHashMap<>();
|
||||||
long totalBytes;
|
long totalBytes;
|
||||||
|
String fsType = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// detekcja po zawartości, nie po rozszerzeniu — .iso bywa obrazem UDF
|
||||||
|
fsType = fsTypeName(FileSystemDetector.detectFileSystemType(source));
|
||||||
IFileSystem fs = AssetSourceDispatcher.openAssets(source);
|
IFileSystem fs = AssetSourceDispatcher.openAssets(source);
|
||||||
totalBytes = walk(fs, "", sha1ByPath, sizeByPath, formatByPath);
|
totalBytes = walk(fs, "", sha1ByPath, sizeByPath, formatByPath);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// np. Wojna Trojańska.iso — obraz UDF, którego IsoFileSystem nie parsuje
|
|
||||||
String note = e.getMessage() == null ? e.toString() : e.getMessage();
|
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());
|
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();
|
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);
|
sha1ByPath, sizeByPath, formatByPath, roots);
|
||||||
|
|
||||||
List<RootSummary> summaries = new ArrayList<>();
|
List<RootSummary> summaries = new ArrayList<>();
|
||||||
@@ -93,6 +98,16 @@ public final class Ingestor {
|
|||||||
.orElse(null);
|
.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 {
|
private static String containerType(File source) throws IOException {
|
||||||
if (source.isDirectory()) {
|
if (source.isDirectory()) {
|
||||||
return "dir";
|
return "dir";
|
||||||
@@ -154,13 +169,13 @@ public final class Ingestor {
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persistUnreadable(File source, String container, String sha256, String note)
|
private void persistUnreadable(File source, String container, String fsType,
|
||||||
throws Exception {
|
String sha256, String note) throws Exception {
|
||||||
Connection conn = db.connection();
|
Connection conn = db.connection();
|
||||||
boolean auto = conn.getAutoCommit();
|
boolean auto = conn.getAutoCommit();
|
||||||
conn.setAutoCommit(false);
|
conn.setAutoCommit(false);
|
||||||
try {
|
try {
|
||||||
upsertCopy(conn, source, container, "unreadable", note,
|
upsertCopy(conn, source, container, fsType, "unreadable", note,
|
||||||
source.length(), sha256, 0);
|
source.length(), sha256, 0);
|
||||||
conn.commit();
|
conn.commit();
|
||||||
} catch (Exception e) {
|
} 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,
|
long size, String sha256,
|
||||||
Map<String, String> sha1ByPath,
|
Map<String, String> sha1ByPath,
|
||||||
Map<String, Long> sizeByPath,
|
Map<String, Long> sizeByPath,
|
||||||
@@ -181,7 +197,7 @@ public final class Ingestor {
|
|||||||
boolean previousAutoCommit = conn.getAutoCommit();
|
boolean previousAutoCommit = conn.getAutoCommit();
|
||||||
conn.setAutoCommit(false);
|
conn.setAutoCommit(false);
|
||||||
try {
|
try {
|
||||||
long copyId = upsertCopy(conn, source, container, status, statusNote,
|
long copyId = upsertCopy(conn, source, container, fsType, status, statusNote,
|
||||||
size, sha256, sha1ByPath.size());
|
size, sha256, sha1ByPath.size());
|
||||||
|
|
||||||
try (Statement st = conn.createStatement()) {
|
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,
|
String status, String statusNote,
|
||||||
long size, String sha256, int fileCount) throws Exception {
|
long size, String sha256, int fileCount) throws Exception {
|
||||||
String path = source.getAbsolutePath();
|
String path = source.getAbsolutePath();
|
||||||
try (PreparedStatement st = conn.prepareStatement("""
|
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)
|
size, sha256, file_count, ingested_at, core_version)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT(path) DO UPDATE SET
|
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,
|
status_note = excluded.status_note, size = excluded.size,
|
||||||
sha256 = excluded.sha256, file_count = excluded.file_count,
|
sha256 = excluded.sha256, file_count = excluded.file_count,
|
||||||
ingested_at = excluded.ingested_at, core_version = excluded.core_version
|
ingested_at = excluded.ingested_at, core_version = excluded.core_version
|
||||||
""")) {
|
""")) {
|
||||||
st.setString(1, container);
|
st.setString(1, container);
|
||||||
st.setString(2, status);
|
st.setString(2, fsType);
|
||||||
st.setString(3, statusNote);
|
st.setString(3, status);
|
||||||
st.setString(4, path);
|
st.setString(4, statusNote);
|
||||||
st.setString(5, source.getName());
|
st.setString(5, path);
|
||||||
st.setLong(6, size);
|
st.setString(6, source.getName());
|
||||||
st.setString(7, sha256);
|
st.setLong(7, size);
|
||||||
st.setInt(8, fileCount);
|
st.setString(8, sha256);
|
||||||
st.setString(9, Instant.now().toString());
|
st.setInt(9, fileCount);
|
||||||
st.setString(10, coreVersion);
|
st.setString(10, Instant.now().toString());
|
||||||
|
st.setString(11, coreVersion);
|
||||||
st.executeUpdate();
|
st.executeUpdate();
|
||||||
}
|
}
|
||||||
try (PreparedStatement st = conn.prepareStatement("SELECT id FROM copy WHERE path = ?")) {
|
try (PreparedStatement st = conn.prepareStatement("SELECT id FROM copy WHERE path = ?")) {
|
||||||
|
|||||||
Vendored
+1
-1
Submodule vendor/Rex-EMoolator updated: 8140276580...23f3f65100
Reference in New Issue
Block a user