diff --git a/app/ui/features/upscale/index.js b/app/ui/features/upscale/index.js index d8b90483..ba0b9237 100644 --- a/app/ui/features/upscale/index.js +++ b/app/ui/features/upscale/index.js @@ -35,21 +35,23 @@ export default { } async function run(media, pressed = false) { - // RTX only counts when this machine has it on right now: a project saved elsewhere may still say "rtx" - const rtxHere = rtxOn() && await app.api.upscaleModels().then((r) => Boolean(r.rtx)).catch(() => false); - const model = rtxOn() ? (rtxHere ? RTX : "") : p.pref("upscaleModel", ""); if (!media || media.subfolder === UPSCALED) return; // never upscale twice - if (!model) return pressed ? suggest() : c.toast.warn({ text: "Upscale: pick a model or NVIDIA RTX Super Resolution in Settings ▸ Editor." }); const key = keyOf(media), pid = p.project.id; if (busy.has(key)) return; - busy.add(key); draw(); c.toast.info({ text: `Upscaling with ${model}…` }); + busy.add(key); draw(); try { + // RTX only counts when this machine has it on right now: a project saved elsewhere may still say "rtx" + if (rtxOn() && !(await app.api.upscaleModels().then((r) => Boolean(r.rtx)).catch(() => false))) + return void c.toast.warn({ text: `${RTX} is not available on this machine (or is switched off in Settings ▸ Modules). Pick Upscale model as the Engine in Settings ▸ Editor.` }); + const model = rtxOn() ? RTX : p.pref("upscaleModel", ""); + if (!model) return void (pressed ? suggest() : c.toast.warn({ text: "Upscale: pick a model or NVIDIA RTX Super Resolution in Settings ▸ Editor." })); + c.toast.info({ text: `Upscaling with ${model}…` }); const out = await upscale(app.api, media, model, undefined, rtxOn() ? rtxOpts() : undefined); const swapped = await p.editFor(pid, (pr) => swap(pr, media, out) > 0); if (!swapped) c.toast.warn({ text: "Upscaled, but that render was replaced meanwhile, so nothing was swapped." }); else c.toast.good({ text: "Upscaled: the render now plays the larger video." }); } catch (err) { c.toast.warn({ text: `Upscale failed: ${err.message}` }); } - busy.delete(key); draw(); + finally { busy.delete(key); draw(); } } function draw() { diff --git a/conftest.py b/conftest.py index d0b46cee..bacfb569 100644 --- a/conftest.py +++ b/conftest.py @@ -32,6 +32,13 @@ def _comfyui_root(): COMFYUI = _comfyui_root() +@pytest.fixture(autouse=True) +def _own_addons_file(tmp_path, monkeypatch): + """A developer's own addons.json must not decide what a test sees.""" + from core import config + monkeypatch.setattr(config, "ADDONS_FILE", tmp_path / "addons.json") + + @pytest.fixture(scope="session") def comfyui(): """ComfyUI's source tree on sys.path, or skip. diff --git a/core/addons.py b/core/addons.py index 7448cbb2..e51a73b4 100644 --- a/core/addons.py +++ b/core/addons.py @@ -81,7 +81,7 @@ def _wanted(data) -> set: def enabled(addon_id: str) -> bool: """Wanted and installed: the only state in which code may use the add-on.""" a = _BY_ID.get(addon_id) - return bool(a) and addon_id in _wanted(_read()) and _present(a) + return bool(a) and a["offered"]() and addon_id in _wanted(_read()) and _present(a) def listing() -> list[dict]: diff --git a/core/tests/test_addons.py b/core/tests/test_addons.py index 2dd086c6..b43c5080 100644 --- a/core/tests/test_addons.py +++ b/core/tests/test_addons.py @@ -111,3 +111,11 @@ def test_the_routes_list_and_switch(comfyui, here): assert bad == 400 assert next(a["state"] for a in ok["addons"] if a["id"] == "camera") == "pending" assert json.loads(config.ADDONS_FILE.read_text())["wanted"] == ["camera"] + + +def test_an_add_on_this_machine_cannot_run_is_not_enabled_even_if_wanted_and_installed(here, monkeypatch): + here.add("nvidia_vsr") + addons.set_wanted("nvidia_vsr", True) + assert addons.enabled("nvidia_vsr") + monkeypatch.setitem(addons._BY_ID["nvidia_vsr"], "offered", lambda: False) # the folder moved to a machine without the card + assert not addons.enabled("nvidia_vsr")