diff options
| author | historia <historiavg@proton.me> | 2026-09-01 14:32:05 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-09-01 14:32:05 -0400 |
| commit | 6cfcd564c0684c52618235e6366f4a81c02b9a5b (patch) | |
| tree | 55321760a8103bc6b5d79489fac4135a60e6e3ba /app/ui/taskview.py | |
| parent | dc6e7cd43029da62dabe2513fb5aa8a34df1bd6d (diff) | |
| download | tts-audiobook-generator-6cfcd564c0684c52618235e6366f4a81c02b9a5b.tar.gz | |
slop refactor/dedup
Diffstat (limited to 'app/ui/taskview.py')
| -rw-r--r-- | app/ui/taskview.py | 85 |
1 files changed, 25 insertions, 60 deletions
diff --git a/app/ui/taskview.py b/app/ui/taskview.py index 3831ceb..e280673 100644 --- a/app/ui/taskview.py +++ b/app/ui/taskview.py @@ -53,10 +53,11 @@ from typing import Callable, List, Optional, Tuple import logging_kit from ui import tui +from ui import viewkit from ui.viewkit import (TERMINAL_PHASES as _TERMINAL, DRAW_TIMEOUT_MS as _DRAW_TIMEOUT_MS, - ScreenView, _box, _fit, _format_elapsed, _sep, - _text) + ScreenView, _box, _fit, _format_elapsed, _rect_box, + _sep, _text) # How many recent output lines the tail keeps in memory. The on-screen tail # draws as many as fit (see render); the full run is also mirrored to the @@ -411,9 +412,14 @@ class TaskView(ScreenView): return self._result_rc() def _result_rc(self) -> int: - """The exit code for the whole run (cancelled counts as failure).""" + """The exit code for the whole run: 0 ok, 130 cancelled, else first rc. + + 130 (the CLI's Ctrl-C code) distinguishes a user cancel from a + plain step failure, so callers like the hub can flash "cancelled" + instead of "failed". + """ if self.cancelled: - return 1 + return 130 return next((rc for rc in self.results if rc), 0) def _on_stop(self) -> None: @@ -432,7 +438,7 @@ class TaskView(ScreenView): return False self.cancelling = True self._cancel.set() - self._worker.join(timeout=60) + self._join_worker() return True # ------------------------------------------------------------------ @@ -542,42 +548,13 @@ class TaskView(ScreenView): # Small helpers (module-level for testability) # --------------------------------------------------------------------------- -class _LineWriter: - """A file-like object that forwards writes to a per-line callback. - - Handles carriage-return progress updates (git/tqdm) by treating ``\r`` - as a line terminator too, so the last full line always reflects the - latest progress. - """ - - def __init__(self, emit: Callable[[str], None]): - self._emit = emit - self._buffer = "" - - def write(self, text: str) -> int: - if not text: - return 0 - self._buffer += text - while True: - cut = _find_line_end(self._buffer) - if cut < 0: - break - line, self._buffer = self._buffer[:cut], self._buffer[cut + 1:] - if line: - self._emit(line) - return len(text) - - def flush(self) -> None: - if self._buffer: - self._emit(self._buffer) - self._buffer = "" - - def isatty(self) -> bool: - return False +class _LineWriter(viewkit.LineSplitter): + """A file-like that forwards writes to a per-line callback (see + viewkit.LineSplitter for the \\r/\\n splitting).""" def _find_line_end(text: str) -> int: - """Index of the earliest ``\n`` or ``\r`` in TEXT, else -1.""" + """Index of the earliest ``\\n`` or ``\\r`` in TEXT, else -1.""" newline = text.find("\n") carriage = text.find("\r") if newline < 0: @@ -607,23 +584,6 @@ def _fmt_bytes(size: float) -> str: return f"{value:.1f}GB" -def _rect_box(scr, curses, theme, x: int, y: int, w: int, h: int) -> None: - """Draw a box around the rectangle ``(x, y, w, h)``.""" - border = theme["border"] - try: - scr.addch(y, x, curses.ACS_ULCORNER, border) - scr.addch(y, x + w - 1, curses.ACS_URCORNER, border) - scr.addch(y + h - 1, x, curses.ACS_LLCORNER, border) - scr.addch(y + h - 1, x + w - 1, curses.ACS_LRCORNER, border) - scr.hline(y, x + 1, curses.ACS_HLINE, w - 2, border) - scr.hline(y + h - 1, x + 1, curses.ACS_HLINE, w - 2, border) - for yy in range(y + 1, y + h - 1): - scr.addch(yy, x, curses.ACS_VLINE, border) - scr.addch(yy, x + w - 1, curses.ACS_VLINE, border) - except Exception: - pass - - class _ThreadRouter: """A file-like object that routes writes to a per-thread writer. @@ -846,9 +806,9 @@ class LanesView(_GetchModes): return self._clock() def _result_rc(self) -> int: - """The exit code for the whole run (cancelled counts as failure).""" + """The exit code for the whole run: 0 ok, 130 cancelled, else first rc.""" if self.cancelled: - return 1 + return 130 for lane in self._lanes: for rc in lane.results: if rc: @@ -906,7 +866,11 @@ class LanesView(_GetchModes): return key def _prompt_cancel(self) -> bool: - """Esc/q: confirm cancel, then wait for both workers to wind down.""" + """Esc/q: confirm cancel, then wait for the workers to wind down. + + Best-effort joins (see ScreenView._join_worker): a wedged lane + worker is left to its daemon fate rather than blocking the view. + """ self._blocking() try: answer = tui.confirm(self.scr, "Cancel this step?", default=False, @@ -918,8 +882,9 @@ class LanesView(_GetchModes): self.cancelling = True self._cancel.set() for lane in self._lanes: - if lane.worker is not None: - lane.worker.join(timeout=60) + worker = lane.worker + if worker is not None and worker.is_alive(): + worker.join(timeout=60) return True # -- drawing ----------------------------------------------------- |
