Initialized project with game ingesting
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
package pl.genschu.rexcatalog;
|
||||
|
||||
import pl.genschu.rexcatalog.db.Database;
|
||||
import pl.genschu.rexcatalog.ingest.Ingestor;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CLI katalogu. Krok 1: indeksowanie obrazów do SQLite.
|
||||
* REST + MCP siadają później na tej samej bazie.
|
||||
*/
|
||||
public final class Main {
|
||||
|
||||
private static final String CORE_VERSION =
|
||||
System.getProperty("catalog.coreVersion", "nieznana");
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length == 0) {
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
Path dataDir = Path.of(System.getProperty("catalog.data", "data"));
|
||||
|
||||
try {
|
||||
switch (args[0]) {
|
||||
case "ingest" -> ingest(dataDir, args);
|
||||
case "list" -> list(dataDir);
|
||||
case "stats" -> stats(dataDir);
|
||||
default -> usage();
|
||||
}
|
||||
} finally {
|
||||
// bez tego proces wisi po zakończeniu pracy — patrz Headless.shutdown()
|
||||
Headless.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ingest(Path dataDir, String[] args) throws Exception {
|
||||
if (args.length < 2) {
|
||||
System.err.println("ingest wymaga co najmniej jednej ścieżki");
|
||||
return;
|
||||
}
|
||||
Headless.boot();
|
||||
|
||||
List<File> sources = new ArrayList<>();
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
sources.addAll(Ingestor.expand(new File(args[i])));
|
||||
}
|
||||
if (sources.isEmpty()) {
|
||||
System.err.println("Nie znaleziono nic do zaindeksowania.");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.printf("Indeksuję %d kopii (core %s)%n%n", sources.size(), CORE_VERSION);
|
||||
|
||||
try (Database db = Database.open(dataDir)) {
|
||||
Ingestor ingestor = new Ingestor(db, CORE_VERSION);
|
||||
int ok = 0;
|
||||
int failed = 0;
|
||||
|
||||
for (File source : sources) {
|
||||
long started = System.currentTimeMillis();
|
||||
System.out.printf(" %-46s ", truncate(source.getName(), 46));
|
||||
System.out.flush();
|
||||
try {
|
||||
Ingestor.Result result = ingestor.ingest(source);
|
||||
long seconds = (System.currentTimeMillis() - started) / 1000;
|
||||
|
||||
if ("unreadable".equals(result.status())) {
|
||||
System.out.printf("NIECZYTELNY: %s%n", result.statusNote());
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
System.out.printf("%5d plików %2ds%n", result.fileCount(), seconds);
|
||||
if (result.roots().isEmpty()) {
|
||||
System.out.printf(" └─ %s%n", "installer".equals(result.status())
|
||||
? "płyta instalacyjna — silnik nierozpoznawalny bez rozpakowania"
|
||||
: "silnik nierozpoznany");
|
||||
}
|
||||
for (Ingestor.RootSummary root : result.roots()) {
|
||||
System.out.printf(" └─ %-12s %-13s %s%n",
|
||||
root.rootPath().isEmpty() ? "(korzeń)" : root.rootPath() + "/",
|
||||
root.engine(),
|
||||
root.edition() == null ? "" : truncate(root.edition(), 44));
|
||||
}
|
||||
ok++;
|
||||
} catch (Exception e) {
|
||||
System.out.printf("BŁĄD: %s%n", e.getMessage());
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
System.out.printf("%nGotowe: %d zaindeksowanych, %d nieudanych.%n", ok, failed);
|
||||
}
|
||||
}
|
||||
|
||||
private static void list(Path dataDir) throws Exception {
|
||||
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,
|
||||
g.root_path,
|
||||
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 = 'family' THEN d.value END) AS family
|
||||
FROM copy c
|
||||
LEFT JOIN game_root g ON g.copy_id = c.id
|
||||
LEFT JOIN detection d ON d.copy_id = c.id
|
||||
AND d.root_path = COALESCE(g.root_path, '')
|
||||
GROUP BY c.id, g.id
|
||||
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");
|
||||
while (rs.next()) {
|
||||
String root = rs.getString("root_path");
|
||||
System.out.printf("%-40s %-10s %-10s %-13s %-22s %s%n",
|
||||
truncate(rs.getString("display_name"), 40),
|
||||
rs.getString("status"),
|
||||
root == null ? "—" : (root.isEmpty() ? "/" : root + "/"),
|
||||
nvl(rs.getString("engine")),
|
||||
nvl(rs.getString("family")),
|
||||
nvl(rs.getString("edition")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void stats(Path dataDir) throws Exception {
|
||||
try (Database db = Database.open(dataDir);
|
||||
Statement st = db.connection().createStatement()) {
|
||||
|
||||
query(st, "kopie", "SELECT COUNT(*) FROM copy");
|
||||
query(st, "pliki (wystąpienia)", "SELECT COUNT(*) FROM file");
|
||||
query(st, "bloby (unikalne)", "SELECT COUNT(*) FROM blob");
|
||||
query(st, "bloby współdzielone przez >1 kopię",
|
||||
"SELECT COUNT(*) FROM (SELECT blob_sha1 FROM file GROUP BY blob_sha1 "
|
||||
+ "HAVING COUNT(DISTINCT copy_id) > 1)");
|
||||
query(st, "zaszyfrowane skrypty", "SELECT COUNT(*) FROM blob WHERE encrypted = 1");
|
||||
|
||||
System.out.println("\nFormaty:");
|
||||
try (ResultSet rs = st.executeQuery(
|
||||
"SELECT format, COUNT(*) n, SUM(size) bytes FROM blob "
|
||||
+ "GROUP BY format ORDER BY n DESC LIMIT 15")) {
|
||||
while (rs.next()) {
|
||||
System.out.printf(" %-8s %6d %8.1f MB%n",
|
||||
nvl(rs.getString("format")), rs.getInt("n"),
|
||||
rs.getLong("bytes") / 1024.0 / 1024.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void query(Statement st, String label, String sql) throws Exception {
|
||||
try (ResultSet rs = st.executeQuery(sql)) {
|
||||
System.out.printf("%-38s %d%n", label + ":", rs.getLong(1));
|
||||
}
|
||||
}
|
||||
|
||||
private static String nvl(String value) {
|
||||
return value == null ? "—" : value;
|
||||
}
|
||||
|
||||
private static String truncate(String value, int max) {
|
||||
if (value == null) {
|
||||
return "—";
|
||||
}
|
||||
return value.length() <= max ? value : value.substring(0, max - 1) + "…";
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.out.println("""
|
||||
rex-catalog — katalog obrazów gier Aidem Media
|
||||
|
||||
ingest <ścieżka...> zaindeksuj obraz, katalog gry lub katalog kolekcji
|
||||
list wypisz zaindeksowane kopie
|
||||
stats statystyki bazy
|
||||
|
||||
Baza: -Dcatalog.data=<katalog> (domyślnie ./data)
|
||||
""");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user