From 268b6734b22cc251bf340882ea9debbd079b9a48 Mon Sep 17 00:00:00 2001 From: historia Date: Wed, 2 Sep 2026 18:36:34 -0400 Subject: rename generator to tts-audiobook-generator. remove pointless upgrade flashes. --- app/selfupdate.py | 14 +++++++-- app/tests/test_hub.py | 55 ++++++++++++++------------------- app/tests/test_selfupdate.py | 21 +++++++++++++ app/ui/hub.py | 73 ++++++++++++++++++-------------------------- 4 files changed, 84 insertions(+), 79 deletions(-) (limited to 'app') diff --git a/app/selfupdate.py b/app/selfupdate.py index 239db92..95e0ef4 100644 --- a/app/selfupdate.py +++ b/app/selfupdate.py @@ -15,8 +15,9 @@ update can never silently overwrite user configuration with upstream's new version. A kept file whose upstream version changed is reported, so new upstream options can be merged by hand. -Run via the hub's Configure Backends > Update Generator action; the -module is stdlib-only like ``backends.common`` / ``backends.envs``. +Run via the hub's Configure Backends > Update tts-audiobook-generator +action; the module is stdlib-only like ``backends.common`` / +``backends.envs``. """ from pathlib import Path @@ -165,7 +166,8 @@ def update_generator(*, emit=None, cancel=None, branch = common.origin_default_branch(base) remote = _rev_parse(base, f"origin/{branch}") if remote is not None and _rev_parse(base, "HEAD") == remote: - _say(f"[INFO] The generator is already up to date ({remote}).", emit) + _say(f"[INFO] tts-audiobook-generator is already up to date " + f"({remote}).", emit) _restore(snapshot, base) return 0 @@ -177,4 +179,10 @@ def update_generator(*, emit=None, cancel=None, for rel in _restore(snapshot, base): _say(f"[OK] Kept your local {rel} (upstream's changes to it were " "skipped — merge new options by hand if needed).", emit) + if not reset_rc: + # The fresh code loads on relaunch (where requirements re-install + # runs automatically) — the console says so because the hub no + # longer flashes after the task view's own summary. + _say("[OK] Restart to apply the update — requirements re-install " + "runs automatically on next launch.", emit) return reset_rc diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py index 228a103..8d5edd9 100644 --- a/app/tests/test_hub.py +++ b/app/tests/test_hub.py @@ -396,7 +396,8 @@ class SubmenuStatusTableTests(unittest.TestCase): def setUp(self): # These tests concern the backend actions; the host's git-checkout - # state (which gates the "Update Generator" entry) must not leak in. + # state (which gates the "Update tts-audiobook-generator" entry) + # must not leak in. patcher = patch.object(hub.selfupdate, "is_git_checkout", return_value=False) patcher.start() @@ -4049,7 +4050,7 @@ class ConfigureBackendsDispatchTests(unittest.TestCase): self.assertEqual(flashes[-1][1], "err") self.assertIn("did not complete", flashes[-1][0]) - def test_update_backends_action_flashes_warn_when_cancelled(self): + def test_update_backends_action_silent_when_cancelled(self): infos = [BackendInfo("qwen", "qwen-tts", lambda: None, lambda: 0, update=lambda **kw: 0)] statuses = [BackendStatus("qwen", "qwen-tts", installed=True, @@ -4057,11 +4058,14 @@ class ConfigureBackendsDispatchTests(unittest.TestCase): patch_flash, flashes = self._capture_flashes() with patch.object(hub, "REGISTRY", infos), \ patch.object(hub, "detect_all", return_value=statuses), \ - patch.object(hub.taskview, "run_steps", return_value=130), \ + patch.object(hub.taskview, "run_steps", + return_value=130) as mk_run, \ patch_flash: hub._update_backends_action(None) - self.assertEqual(flashes[-1][1], "warn") - self.assertIn("cancelled", flashes[-1][0]) + # The task view already showed "cancelled" and waited for a key, + # so the hub adds no flash on top of it. + mk_run.assert_called_once() + self.assertEqual(flashes, []) def test_update_backends_action_without_targets_flashes_a_hint(self): # An installed backend without an update action (and nothing else @@ -4129,8 +4133,8 @@ class ConfigureBackendsDispatchTests(unittest.TestCase): hub._Hub(None).screen_configure() return [opt[0] for opt in captured["options"]] - self.assertIn("Update Generator", options_for(True)) - self.assertNotIn("Update Generator", options_for(False)) + self.assertIn("Update tts-audiobook-generator", options_for(True)) + self.assertNotIn("Update tts-audiobook-generator", options_for(False)) def test_selecting_update_generator_runs_the_action_and_reshows(self): titles = [] @@ -4170,8 +4174,6 @@ class ConfigureBackendsDispatchTests(unittest.TestCase): patch_flash, flashes = self._capture_flashes() with patch.object(hub.selfupdate, "is_git_checkout", return_value=True), \ - patch.object(hub.selfupdate, "current_commit", - return_value="aaa"), \ patch.object(hub.selfupdate, "modified_tracked_files", return_value=["app/converter/config.py"]), \ patch.object(hub.tui, "confirm", @@ -4185,12 +4187,10 @@ class ConfigureBackendsDispatchTests(unittest.TestCase): body = mk_confirm.call_args[1]["body"] self.assertIn("app/converter/config.py", body) - def test_update_self_action_runs_step_and_flashes_update(self): + def test_update_self_action_runs_step_silently_on_success(self): patch_flash, flashes = self._capture_flashes() with patch.object(hub.selfupdate, "is_git_checkout", return_value=True), \ - patch.object(hub.selfupdate, "current_commit", - side_effect=["aaa", "bbb"]), \ patch.object(hub.selfupdate, "modified_tracked_files", return_value=[]), \ patch.object(hub.taskview, "run_steps", @@ -4201,7 +4201,8 @@ class ConfigureBackendsDispatchTests(unittest.TestCase): hub._update_self_action(None) mk_run.assert_called_once() self.assertEqual(mk_run.call_args[0][0], None) - self.assertEqual(mk_run.call_args[0][1], "Update Generator") + self.assertEqual(mk_run.call_args[0][1], + "Update tts-audiobook-generator") steps = mk_run.call_args[0][2] self.assertEqual([step.title for step in steps], ["Fetch and reset the checkout"]) @@ -4211,48 +4212,38 @@ class ConfigureBackendsDispatchTests(unittest.TestCase): steps[0].work(emit, "CANCEL") mk_update.assert_called_once_with(emit=emit, cancel="CANCEL") - self.assertEqual(len(flashes), 1) - text, kind = flashes[0] - self.assertEqual(kind, "ok") - self.assertIn("aaa", text) - self.assertIn("bbb", text) - self.assertIn("Restart to apply", text) + # The task view already showed "completed" and waited for a key; + # the restart reminder lives in the run's console output. + self.assertEqual(flashes, []) - def test_update_self_action_flashes_already_up_to_date(self): + def test_update_self_action_silent_when_already_up_to_date(self): patch_flash, flashes = self._capture_flashes() with patch.object(hub.selfupdate, "is_git_checkout", return_value=True), \ - patch.object(hub.selfupdate, "current_commit", - side_effect=["aaa", "aaa"]), \ patch.object(hub.selfupdate, "modified_tracked_files", return_value=[]), \ patch.object(hub.taskview, "run_steps", return_value=0), \ patch_flash: hub._update_self_action(None) - self.assertEqual(flashes, - [("The generator is already up to date (aaa).", - "ok")]) + # The console already said so; no flash after the key press. + self.assertEqual(flashes, []) - def test_update_self_action_flashes_warn_when_cancelled(self): + def test_update_self_action_silent_when_cancelled(self): patch_flash, flashes = self._capture_flashes() with patch.object(hub.selfupdate, "is_git_checkout", return_value=True), \ - patch.object(hub.selfupdate, "current_commit", - side_effect=["aaa", "aaa"]), \ patch.object(hub.selfupdate, "modified_tracked_files", return_value=[]), \ patch.object(hub.taskview, "run_steps", return_value=130), \ patch_flash: hub._update_self_action(None) - self.assertEqual(flashes[-1][1], "warn") - self.assertIn("cancelled", flashes[-1][0]) + # The task view already showed "cancelled" and waited for a key. + self.assertEqual(flashes, []) def test_update_self_action_flashes_err_when_failed(self): patch_flash, flashes = self._capture_flashes() with patch.object(hub.selfupdate, "is_git_checkout", return_value=True), \ - patch.object(hub.selfupdate, "current_commit", - side_effect=["aaa", "aaa"]), \ patch.object(hub.selfupdate, "modified_tracked_files", return_value=[]), \ patch.object(hub.taskview, "run_steps", return_value=1), \ diff --git a/app/tests/test_selfupdate.py b/app/tests/test_selfupdate.py index bf2f6b3..b69c6e3 100644 --- a/app/tests/test_selfupdate.py +++ b/app/tests/test_selfupdate.py @@ -155,6 +155,9 @@ class UpdateGeneratorTests(unittest.TestCase): self.assertEqual(self.config.read_bytes(), b"# user settings\n") self.assertTrue(any("already up to date" in line for line in self.emitted)) + # A no-op update needs no restart, so no reminder either. + self.assertFalse(any("Restart to apply" in line + for line in self.emitted)) def test_fetch_failure_short_circuits(self): run = self._patch_git(fetch_rc=128) @@ -200,6 +203,24 @@ class UpdateGeneratorTests(unittest.TestCase): self.assertEqual(run.call_args_list[1][1]["emit"], self.emitted.append) + def test_successful_reset_prints_the_restart_reminder(self): + # The hub no longer flashes after the task view's summary, so the + # console output itself must tell the user to restart. + self._patch_git() + rc = selfupdate.update_generator(root=self.root, + emit=self.emitted.append) + self.assertEqual(rc, 0) + self.assertTrue(any("Restart to apply" in line + for line in self.emitted)) + + def test_failed_reset_prints_no_restart_reminder(self): + self._patch_git(reset_rc=1) + rc = selfupdate.update_generator(root=self.root, + emit=self.emitted.append) + self.assertEqual(rc, 1) + self.assertFalse(any("Restart to apply" in line + for line in self.emitted)) + def test_reset_failure_still_restores_the_snapshot(self): self._patch_git(reset_rc=1, on_reset=lambda: self.config.write_bytes( b"# upstream settings\n")) diff --git a/app/ui/hub.py b/app/ui/hub.py index 67680db..f9151f8 100644 --- a/app/ui/hub.py +++ b/app/ui/hub.py @@ -226,7 +226,8 @@ class _Hub: if any(_updatable(info, by_key) for info in REGISTRY): options.append(("Update Backends", "update")) if selfupdate.is_git_checkout(): - options.append(("Update Generator", "update_self")) + options.append(("Update tts-audiobook-generator", + "update_self")) if any(_uninstallable(info, by_key) for info in REGISTRY): options.append(("Uninstall Backend", "uninstall")) @@ -234,8 +235,8 @@ class _Hub: self.stdscr, "Configure Backends", options, back_value=tui.Wizard.BACK, help_lines=["Install, update, configure, or remove a TTS " - "backend. 'Update Generator' refreshes this " - "tool's own checkout instead."], + "backend. 'Update tts-audiobook-generator' " + "refreshes this tool's own checkout instead."], table_rows=_status_rows(statuses), notice_lines=_notice_lines()) if choice is tui.Wizard.BACK: @@ -687,12 +688,12 @@ def _download_models_action(stdscr) -> None: Computes the missing models; when they map to install commands it runs the downloads in the task view (with real byte progress and cancellation) - instead of dropping to the console — a successful run returns silently - (the view already shows [OK] and waits for a key), and only a cancelled - or failed run flashes. When the checkout/server.json is missing, nothing - is missing, or the models do not map to an install command, it flashes - an explanatory notice (the latter explaining how to install each model - by hand). + instead of dropping to the console — the view shows the outcome and + waits for a key, so a successful or cancelled run returns silently and + only a failed run flashes. When the checkout/server.json is missing, + nothing is missing, or the models do not map to an install command, it + flashes an explanatory notice (the latter explaining how to install + each model by hand). """ checkout = audiocpp_backend.find_local_checkout() if checkout is None: @@ -723,10 +724,7 @@ def _download_models_action(stdscr) -> None: rc = taskview.run_steps(stdscr, "Download models", [taskview.TaskStep("Download missing models", run)]) - if rc == 130: - tui.flash(stdscr, "Model download cancelled — re-run it any time.", - "warn") - elif rc: + if rc not in (0, 130): tui.flash(stdscr, "Some model downloads failed. Re-run 'Download " "Missing Models' or install them by hand (see the log).", "err") @@ -741,9 +739,9 @@ def _update_backends_action(stdscr) -> None: git fetch + hard reset for the checkouts, with audio.cpp's binary rebuilt when its checkout moved. A failing backend's step is marked [FAIL] and the remaining backends still update (the run's exit code - is the first failure). A successful run returns silently (the view - already shows [OK] and waits for a key); only a cancelled or failed - run flashes. The status table re-detects when the menu re-shows. + is the first failure). Successful and cancelled runs return silently + (the view already shows the outcome and waits for a key); only a + failed run flashes. The status table re-detects when the menu re-shows. """ statuses = detect_all() by_key = {st.key: st for st in statuses} @@ -760,17 +758,14 @@ def _update_backends_action(stdscr) -> None: steps = [taskview.TaskStep(f"Update {info.label}", make_work(info)) for info in targets] rc = taskview.run_steps(stdscr, "Update Backends", steps) - if rc == 130: - tui.flash(stdscr, "Update cancelled — re-run 'Update Backends' " - "any time.", "warn") - elif rc: + if rc not in (0, 130): tui.flash(stdscr, "Some updates did not complete (failed or " "cancelled) — see the log above. Re-run 'Update " "Backends' to retry.", "err") def _update_self_action(stdscr) -> None: - """Run the "Update Generator" action inside the TUI. + """Run the "Update tts-audiobook-generator" action inside the TUI. Moves the generator's own git checkout to the remote's default-branch HEAD (fetch + hard reset — the same flow the backend checkouts use). @@ -781,17 +776,17 @@ def _update_self_action(stdscr) -> None: and written back afterwards (a kept file whose upstream version changed is reported for a manual merge). A dirty checkout confirms first (declining, Esc included, aborts before anything runs); the run - itself streams in the task view like the other inline actions. After: - a successful run flashes the moved commit range (or "already up to - date") plus the restart reminder — the fresh code loads on relaunch, - where requirements re-install runs automatically — while cancel and - failure flash their usual warn/err. The menu re-shows either way. + itself streams in the task view like the other inline actions, whose + outcome summary the user dismisses with a key — so a successful run + (including a no-op "already up to date" one) flashes nothing, and the + restart reminder is part of the run's own console output (update_generator + emits it after the reset). Only a failed run flashes; the menu re-shows + either way. """ if not selfupdate.is_git_checkout(): tui.flash(stdscr, "This install is not a git checkout — update by " "re-cloning the repository.", "err") return - before = selfupdate.current_commit() modified = selfupdate.modified_tracked_files() if modified: body = ["These locally modified files are kept as they are:", @@ -799,30 +794,20 @@ def _update_self_action(stdscr) -> None: body += modified[:6] if len(modified) > 6: body.append(f"...and {len(modified) - 6} more") - if tui.confirm(stdscr, "Update the generator?", body=body, - default=False, cancel_value=False) is not True: + if tui.confirm(stdscr, "Update tts-audiobook-generator?", + body=body, default=False, + cancel_value=False) is not True: return def work(emit, cancel): return selfupdate.update_generator(emit=emit, cancel=cancel) rc = taskview.run_steps( - stdscr, "Update Generator", + stdscr, "Update tts-audiobook-generator", [taskview.TaskStep("Fetch and reset the checkout", work)]) - after = selfupdate.current_commit() - if rc == 130: - tui.flash(stdscr, "Update cancelled — re-run 'Update Generator' " - "any time.", "warn") - elif rc: - tui.flash(stdscr, "The generator update did not complete — see the " - "log above and retry.", "err") - elif before is not None and before == after: - tui.flash(stdscr, f"The generator is already up to date ({before}).", - "ok") - else: - tui.flash(stdscr, f"Generator updated {before or '?'} → {after}. " - "Restart to apply — requirements re-install runs " - "automatically on next launch.", "ok") + if rc not in (0, 130): + tui.flash(stdscr, "The tts-audiobook-generator update did not " + "complete — see the log above and retry.", "err") def _status_mark(status: Optional[BackendStatus]) -> Tuple[str, str, str]: -- cgit v1.2.3