Egy áruházmotor beolvas egy WarpEngine-katalógust, és kiírja valamelyik gazdaplatform saját játékkönyvtár-formátumát. Csak ez az utolsó lépés tér el platformonként. Az API-val való beszélgetés, a kiadás kiválasztása, a gép illesztése és annak nyilvántartása, hogy mi hova került, mindenhol ugyanaz a munka — a
warpstorepontosan ez a munka, egyetlen fájlban, minden áruházmotor közösen használja.
┌──────────────────────────────┐
│ warpstore.py │
│ catalog · releases · host │
│ state · config · HTTP │
└──────────────┬───────────────┘
┌──────────────┴───────────────┐
▼ ▼
warp-engine-batocera-store warp-engine-retroarch-store
ROM-mappák + gamelist.xml .lpl lejátszási listák + bélyegképek
│ │
▼ ▼
EmulationStation RetroArch
Repó: https://git.teletypegames.org/engines/warpstore Katalógusoldal: WarpEngine Használói: Batocera áruházmotor · RetroArch áruházmotor
A Batocera-áruház 1073 sor volt, és ennek csak nagyjából a harmada szólt a Batoceráról. A RetroArch-áruháznak a másik kétharmadra volt szüksége, ezért a közös rész kiköltözött — már egy második fogyasztóval a kézben, hogy a vágás ott legyen, ahol a két motor valóban eltér.
Egyetlen fájl, csak a Python 3 szabványkönyvtárával: a gazdagépek a Batocera-doboztól (python3, pip nélkül) az asztali gépig terjednek, és egyiket sem szabad rávenni bármi telepítésére. Egy motor telepítője a warpstore.py fájlt az adapterszkript mellé teszi, az adapter pedig import warpstore-t hív.
DEFAULT_CONFIG — hol vannak a dolgok ezen a gazdagépen, és melyik katalógusplatform mire képződik le.accept(spec, sw) — mely katalógusbejegyzéseket tudja futtatni ez a gazdagép, és hogyan csoportosulnak.import warpstore as ws
def accept(spec, sw):
"""Return (scope, extras), or raise ws.Skip(reason) to reject the title."""
if spec["system"] not in available_systems:
raise ws.Skip(f"no '{spec['system']}' ROM folder on this box")
return spec["system"], {"system": spec["system"]}
cfg = ws.load_config(path, DEFAULT_CONFIG, required=("paths.subfolder",))
ws.init(path)
games, skipped = ws.select_games(cfg, ws.fetch_catalog(cfg, use_cache=True), accept)
A scope az, ahogyan a gazdagép csoportosítja a könyvtárát — egy Batocera-rendszer, egy RetroArch-lejátszási lista. A state.json kulcsa <scope>:<name>, így ugyanaz a szoftvernév két scope-ban két külön bejegyzés marad.
| Terület | Függvények |
|---|---|
| Naplózás | set_tag, set_verbose, log, debug, die |
| Fájlok | load_json, write_json, write_atomic, within, prune_empty_dirs |
| Konfiguráció | deep_merge, load_config |
| Áruház-könyvtár | init, majd HOME, CONFIG_PATH, STATE_PATH, CATALOG_CACHE |
| HTTP | http_get, http_download, api_url, download_url, user_agent |
| Gazdagép | machine, host_os, host, resolve_for_host |
| Katalógus | fetch_catalog, select_games, survey_catalog, pick_release, asset_basename, download_image |
| Állapot | load_state, save_state, game_key, record_scope, records_by_scope, match_keys, limit_to_names |
Öt közülük olyan döntést hordoz, amit érdemes ismerni.
select_games kontra survey_catalog — a select_games azt adja vissza, amit az áruház telepíteni tud. A survey_catalog ezen felül címenként egy rekordot ad azokról is, amelyeket ez a gép nem tud telepíteni, egy reason kóddal (platform_off, host_asset, no_asset, vetoed) és egy mondattal. Egy kliens ezeket kiszürkítheti elrejtés helyett — a WarpEngine Client pontosan ezt teszi. Azok a címek, amelyeket az áruház maga dönt úgy, hogy nem kínál, egyik listában sincsenek benne — egy rossz státusz vagy egy kizárólista szerkesztői döntés, nem a gép korlátja.
resolve_for_host(value, host) — olyan konfigurációs érték, amely függhet a géptől. Egy sima sztring mindenhol ugyanaz — ilyen a cartridge: adat egy emulátornak. Egyébként az érték egy mapping, és a legspecifikusabb kulcs nyer:
{"linux-aarch64": …, "aarch64": …, "linux": …, "*": …}
Ha nincs illeszkedő kulcs és nincs * sem, az eredmény None, és a hívó kihagyottként jelenti a címet, ahelyett hogy olyasmit telepítene, ami nem tud futni. Ez a Batocera-motor resolve_for_arch függvényének általánosítása: a Batocera mindig csak linux volt, de egy RetroArch-gazdagép lehet Windows, macOS vagy Android — és ez dönti el, hogy hogyan hívják a libretro core fájlját (.dll, .dylib, _android.so).
within(path, root) — minden törlést ez őriz. Egy áruház csak abból a részfából távolíthat el fájlokat, amely az övé, soha nem a felhasználó könyvtárából és soha nem egy másik áruházéból.
prune_empty_dirs(dirs, root) — ugyanennek a szabálynak az eltávolítási oldala. Csak üres könyvtárak tűnnek el, és csak a root belsejében: egyetlen kósza fájl megtartja a könyvtárat. Ez teszi lehetővé, hogy a purge (és így az uninstall.sh) úgy hagyja a gazdagépet, ahogy volt.
write_atomic — semmi nem íródik a helyén. Egy szinkron közben megszakított áruház különben félig megírt lejátszási listát vagy gamelistet hagyna maga után, ami rosszabb, mint egy régi.
curl -fsSL https://git.teletypegames.org/engines/warpstore/raw/branch/master/uninstall.sh | sh
Ezzel az egész keretrendszer lehúzható anélkül, hogy fejben kellene tartani, mi van telepítve: megkeresi az összes áruház-könyvtárat az ismert gyökerek alatt, mindegyik áruház saját motorjával takaríttatja el, amit telepített, majd törli az áruházat, az indítóját és a motorfájlokat. Egyetlen áruházhoz inkább az adott motor saját uninstall.sh szkriptjét használd — az ismeri a gazdagép extráit is, például egy Batocera Ports-bejegyzést.
| Környezeti változó | Jelentés |
|---|---|
DRY_RUN |
1 esetén kiírja, mi tűnne el, és semmit nem távolít el |
KEEP_GAMES |
1 esetén csak a szkripteket törli, a játékokat megtartja |
FORCE |
1 esetén futó RetroArch mellett is takarít |
BATOCERA_STORE_ROOT, BATOCERA_PORTS_DIR |
hol keressen egy Batocera-dobozon |
STORE_ROOT, BIN_DIR |
hol keressen egy asztali gépen |
Ha egy motor nem tud végezni (fut a RetroArch, nincs felcsatolva a ROM-gyökér), a futás ott megáll, ahelyett hogy törölné azt a motort, amely tudja, mit telepített.
A szkript POSIX sh, nem bash, így a | sh ott is működik, ahol a /bin/sh a dash. A motorok saját telepítői ugyanígy.
state.json, 2-es verzió, <scope>:<name> kulccsal:
{
"version": 2,
"installed": {
"c64:blessingofra": { "name": "blessingofra", "scope": "c64", "asset": "blessingofra-2.0.0.prg", "…": "…" }
}
}
A record_scope() visszaesik a rekord system mezőjére, mert a Batocera-áruház ezt írta, mielőtt ez a modul létezett. Ott a kettő ugyanaz a sztring volt, így egy telepített doboz migráció nélkül működik tovább, és semmit nem tölt le újra. Egy version: 1 fájlt, amelynek a kulcsa a csupasz szoftvernév, az első futáskor újrakulcsoz.
Egyik motor sem rögzít verziót: mindkettő a master ágról húzza le a warpstore.py fájlt telepítéskor, így egy itteni változás a következő telepítéskor minden áruházhoz eljut. Pont ez a lényeg, és pont ezért kell a felületet kicsin tartani. A --version mindkét motoron kiírja mindkét számot, például 3.1.0 (warpstore 1.2.0).