fix: trigger RomM quick scan via socket
This commit is contained in:
@@ -18,7 +18,9 @@ import json
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime, timezone
|
||||
@@ -41,6 +43,8 @@ ROMM_SERVER_URL = os.environ.get("ROMM_SERVER_URL", "").rstrip("/")
|
||||
ROMM_USERNAME = os.environ.get("ROMM_USERNAME", "")
|
||||
ROMM_PASSWORD = os.environ.get("ROMM_PASSWORD", "")
|
||||
ROMM_API_TOKEN = os.environ.get("ROMM_API_TOKEN", "")
|
||||
ROMM_SCAN_TASK = os.environ.get("ROMM_SCAN_TASK", "socket_quick_scan")
|
||||
ROMM_SCAN_APIS = [api.strip() for api in os.environ.get("ROMM_SCAN_APIS", "igdb").split(",") if api.strip()]
|
||||
|
||||
IGNORE_SUFFIXES = {".part", ".partial", ".tmp", ".crdownload"}
|
||||
|
||||
@@ -219,12 +223,10 @@ def romm_token() -> str:
|
||||
return token
|
||||
|
||||
|
||||
def trigger_romm_scan() -> None:
|
||||
if not SCAN_AFTER_IMPORT:
|
||||
return
|
||||
def trigger_romm_scan_via_task_api(task_name: str) -> bool:
|
||||
token = romm_token()
|
||||
req = request.Request(
|
||||
f"{ROMM_SERVER_URL}/api/tasks/run/scan_library",
|
||||
f"{ROMM_SERVER_URL}/api/tasks/run/{task_name}",
|
||||
data=b"{}",
|
||||
method="POST",
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
@@ -232,16 +234,92 @@ def trigger_romm_scan() -> None:
|
||||
try:
|
||||
with request.urlopen(req, timeout=30) as resp:
|
||||
body = resp.read().decode("utf-8", errors="replace")
|
||||
log(f"triggered RomM scan_library: HTTP {resp.status} {body[:300]}")
|
||||
log(f"triggered RomM task {task_name}: HTTP {resp.status} {body[:300]}")
|
||||
return True
|
||||
except error.HTTPError as exc:
|
||||
body = exc.read().decode("utf-8", errors="replace")
|
||||
if exc.code == 403:
|
||||
raise RuntimeError(
|
||||
"RomM scan trigger failed: HTTP 403 Forbidden. The RomM credential/token can log in, "
|
||||
"but it does not have the tasks.run scope required to run scan_library. "
|
||||
"but it does not have the tasks.run scope required to run tasks. "
|
||||
"Create a RomM API token with Run tasks/tasks.run permission and set ROMM_API_TOKEN."
|
||||
) from exc
|
||||
raise RuntimeError(f"RomM scan trigger failed: HTTP {exc.code} {body}") from exc
|
||||
log(f"RomM task API trigger {task_name} did not run: HTTP {exc.code} {body[:300]}")
|
||||
return False
|
||||
|
||||
|
||||
def trigger_romm_scan_via_socket() -> None:
|
||||
"""Trigger the same Socket.IO quick scan used by RomM's web UI.
|
||||
|
||||
Current RomM versions expose /api/tasks/run/scan_library, but that scheduled
|
||||
task is not manual-runnable unless RomM's scheduled rescan is enabled. The web
|
||||
UI manual scan uses the /ws/socket.io "scan" event instead.
|
||||
"""
|
||||
if not ROMM_SERVER_URL:
|
||||
raise RuntimeError("ROMM_SERVER_URL is required for socket quick scan")
|
||||
|
||||
options = {
|
||||
"platforms": [],
|
||||
"type": "quick",
|
||||
"roms_ids": [],
|
||||
"apis": ROMM_SCAN_APIS,
|
||||
"launchbox_remote_enabled": True,
|
||||
}
|
||||
node_script = r"""
|
||||
const { io } = require('socket.io-client');
|
||||
const url = process.env.ROMM_SERVER_URL;
|
||||
const options = JSON.parse(process.env.ROMM_SOCKET_SCAN_OPTIONS || '{}');
|
||||
const socket = io(url, { path: '/ws/socket.io', transports: ['websocket'], timeout: 10000 });
|
||||
socket.on('connect', () => {
|
||||
console.log('socket_connected');
|
||||
socket.emit('scan', options);
|
||||
setTimeout(() => { console.log('socket_scan_emitted'); socket.close(); process.exit(0); }, 2000);
|
||||
});
|
||||
socket.on('connect_error', (err) => {
|
||||
console.error('socket_connect_error: ' + err.message);
|
||||
process.exit(2);
|
||||
});
|
||||
"""
|
||||
with tempfile.NamedTemporaryFile("w", suffix=".js", delete=False) as fh:
|
||||
fh.write(node_script)
|
||||
script_path = fh.name
|
||||
try:
|
||||
env = os.environ.copy()
|
||||
env["ROMM_SOCKET_SCAN_OPTIONS"] = json.dumps(options)
|
||||
try:
|
||||
npm_root = subprocess.check_output(["npm", "root", "-g"], text=True, timeout=10).strip()
|
||||
env["NODE_PATH"] = npm_root
|
||||
except Exception:
|
||||
pass
|
||||
result = subprocess.run(
|
||||
["node", script_path],
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
timeout=30,
|
||||
check=False,
|
||||
)
|
||||
output = (result.stdout + result.stderr).strip().replace("\n", " | ")
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"RomM socket quick scan failed rc={result.returncode}: {output}")
|
||||
log(f"triggered RomM socket quick scan: {output}")
|
||||
finally:
|
||||
try:
|
||||
Path(script_path).unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def trigger_romm_scan() -> None:
|
||||
if not SCAN_AFTER_IMPORT:
|
||||
return
|
||||
|
||||
if ROMM_SCAN_TASK and ROMM_SCAN_TASK != "socket_quick_scan":
|
||||
if trigger_romm_scan_via_task_api(ROMM_SCAN_TASK):
|
||||
return
|
||||
|
||||
# Preferred/default for this RomM install: mirrors the UI's manual quick scan.
|
||||
trigger_romm_scan_via_socket()
|
||||
|
||||
|
||||
def import_file(path: Path) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user