diff options
Diffstat (limited to 'app/ui/runview.py')
| -rw-r--r-- | app/ui/runview.py | 53 |
1 files changed, 51 insertions, 2 deletions
diff --git a/app/ui/runview.py b/app/ui/runview.py index ff7cfb2..0c4b0a8 100644 --- a/app/ui/runview.py +++ b/app/ui/runview.py @@ -152,6 +152,7 @@ class RunView(ScreenView): self.convert_started: Optional[float] = None self.stop_started: Optional[float] = None self.server_log_path = "" + self.boot_hint = "" # known-crash hint from an exited/timeout boot # -- threads --------------------------------------------------- self._monitor_stop = threading.Event() self._worker = threading.Thread(target=self._worker_main, @@ -183,6 +184,8 @@ class RunView(ScreenView): "timeout": "server did not become ready in time", }[kind] self.log_tail = list(event.get("log_tail") or []) + self.boot_hint = event.get("hint") or "" + self._record_boot_failure() self._finish("error") elif kind == "cancelled": self.cancelled = True @@ -509,13 +512,42 @@ class RunView(ScreenView): common.record_post_tui_notice(self._summary_text()) return True + def _record_boot_failure(self) -> None: + """Write a boot failure into the dated run log. + + The boot's output lives in the server's own log and its events + reached only this view, so without this the dated log the failure + pointers name would stay empty — "Full details in the log file" + must never point at a blank file. Best-effort: write errors are + swallowed (the error screen still carries everything). + """ + path = self.config.log_path + if not path: + return + stamp = f"{datetime.now():%Y-%m-%d %H:%M:%S}" + try: + with open(path, "a", encoding="utf-8") as logf: + logf.write(f"{stamp} - ERROR - {self.server_message}\n") + if self.boot_hint: + logf.write(f"{stamp} - WARNING - hint: " + f"{self.boot_hint}\n") + if self.server_log_path: + logf.write(f"{stamp} - INFO - the server's own output " + f"is in {self.server_log_path}\n") + except (OSError, ValueError): + pass + def _summary_text(self) -> str: """The results summary printed after the TUI exits. Output directory, one line per book with its generated file names and OK/FAIL status (plus the failure detail), a success count, and - the total elapsed time. A failed run ends with the converter's log - file path, where the details behind the [FAIL] lines live. + the total elapsed time. A run that died before any book started + (a failed boot, a refused connection) names the reason, the known + crash hint, and the server's own log, because "No books were + converted" alone would hide why. A failed run ends with the + converter's log file path, where the details behind the [FAIL] + lines live. """ from converter.converter import AUDIOBOOKS_FOLDER lines = ["Audiobook generation finished", @@ -534,6 +566,14 @@ class RunView(ScreenView): f"successfully") else: lines.append("No books were converted") + if self.phase == "error": + reason = self.error_message or self.server_message + if reason: + lines.append(f"Failure: {reason}") + if self.boot_hint: + lines.append(f"hint: {self.boot_hint}") + if self.server_log_path: + lines.append(f"server log: {self.server_log_path}") started = self.convert_started or self.boot_started finished = self.finished_at or self._now() elapsed = finished - (started if started is not None else finished) @@ -745,12 +785,21 @@ class RunView(ScreenView): for line in _wrap(detail, width - inner_x - 3)[:2]: _text(scr, theme, y, inner_x, line, theme["err"]) y += 1 + if self.boot_hint: + for line in _wrap(self.boot_hint, width - inner_x - 3)[:2]: + _text(scr, theme, y, inner_x, line, theme["warn"]) + y += 1 if self.log_tail: for line in self.log_tail[:3]: _text(scr, theme, y, inner_x, _fit(line.strip() or " ", width - inner_x - 3), theme["dim"]) y += 1 + if self.server_log_path: + _text(scr, theme, y, inner_x, + _fit(f"server log: {self.server_log_path}", + width - inner_x - 3), theme["dim"]) + y += 1 if self.config.log_path: _text(scr, theme, y, inner_x, _fit(f"details: {self.config.log_path}", |
