aboutsummaryrefslogtreecommitdiff
path: root/app/backends/common.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-03 17:26:14 -0400
committerhistoria <historiavg@proton.me>2026-09-03 17:26:14 -0400
commit9dd66997033f7d306718c67745b41c16dd034865 (patch)
tree201385090c547d83cf9c60f6865b28ef1c67a927 /app/backends/common.py
parent976f12a72ebbd330e0328991afed3524c06a35ad (diff)
downloadtts-audiobook-generator-9dd66997033f7d306718c67745b41c16dd034865.tar.gz
feat: pip install checks for bad crc errors and purges cache automatically
Diffstat (limited to 'app/backends/common.py')
-rw-r--r--app/backends/common.py38
1 files changed, 33 insertions, 5 deletions
diff --git a/app/backends/common.py b/app/backends/common.py
index b5d6576..a12a366 100644
--- a/app/backends/common.py
+++ b/app/backends/common.py
@@ -372,7 +372,8 @@ def write_prompt_text(wav_dir: Path,
def run_console_subprocess(argv: List[str], cwd: Optional[Path] = None,
*, emit=None, cancel=None, on_cancel=None,
stall_timeout: Optional[float] = None,
- env: Optional[Dict[str, str]] = None) -> int:
+ env: Optional[Dict[str, str]] = None,
+ on_chunk=None) -> int:
"""Run a subprocess, streaming output to the console or to EMIT.
With EMIT None the child inherits the real terminal and its output
@@ -382,6 +383,15 @@ def run_console_subprocess(argv: List[str], cwd: Optional[Path] = None,
updates like git's or tqdm's surface as lines), and each line is passed
to EMIT — the in-TUI task view path.
+ ON_CHUNK (a ``callable(bytes)``) asks for the child's output *and* a
+ look at its raw bytes: the run then takes the piped path even without
+ EMIT, forwarding every chunk verbatim to the real terminal (so
+ carriage-return progress still animates) and handing each chunk to
+ ON_CHUNK — the hook pip_install uses to sniff pip's output for a
+ corrupted-wheel failure while the user watches it live. ON_CHUNK must
+ not raise and must not write to the terminal (the forwarding here
+ already does).
+
CANCEL is an optional ``threading.Event``: once set, ON_CANCEL (if given)
is called (e.g. to touch a ``--cancel-file``), then the child's process
group is terminated (SIGTERM, escalating to SIGKILL after a grace
@@ -401,7 +411,7 @@ def run_console_subprocess(argv: List[str], cwd: Optional[Path] = None,
Returns the process exit code.
"""
import subprocess
- if emit is None:
+ if emit is None and on_chunk is None:
try:
result = subprocess.run(
argv, cwd=str(cwd) if cwd is not None else None, env=env)
@@ -423,7 +433,11 @@ def run_console_subprocess(argv: List[str], cwd: Optional[Path] = None,
try:
proc = subprocess.Popen(argv, **popen_kwargs)
except OSError as exc:
- emit(f"[ERROR] Could not run {' '.join(argv)}: {exc}")
+ message = f"[ERROR] Could not run {' '.join(argv)}: {exc}"
+ if emit is not None:
+ emit(message)
+ else:
+ print(message)
return 1
cancelled = False
@@ -448,6 +462,16 @@ def run_console_subprocess(argv: List[str], cwd: Optional[Path] = None,
if not chunk:
break
last_output = time.monotonic()
+ if on_chunk is not None:
+ on_chunk(chunk)
+ if emit is None:
+ # The on_chunk console path: forward each chunk
+ # verbatim so carriage-return progress bars still
+ # animate exactly as they did when the child owned
+ # the terminal.
+ sys.stdout.write(chunk.decode("utf-8", errors="replace"))
+ sys.stdout.flush()
+ continue
pending += chunk.decode("utf-8", errors="replace")
parts = re.split(r"[\r\n]+", pending)
pending = parts.pop()
@@ -483,8 +507,12 @@ def run_console_subprocess(argv: List[str], cwd: Optional[Path] = None,
if (stall_timeout is not None
and time.monotonic() - last_output > stall_timeout):
stalled = True
- emit(f"[ERROR] No output for {int(stall_timeout)}s — assuming "
- "the process hung; stopping it.")
+ message = (f"[ERROR] No output for {int(stall_timeout)}s — "
+ "assuming the process hung; stopping it.")
+ if emit is not None:
+ emit(message)
+ else:
+ print(message)
_terminate_process_group(proc)
break
time.sleep(0.1)