Files
rex-catalog/src/main/java/pl/genschu/rexcatalog/Main.java
T

187 lines
7.6 KiB
Java

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.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,
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 %-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 %-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")),
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)
""");
}
}