aboutsummaryrefslogtreecommitdiff
path: root/app/ui/runview.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/ui/runview.py')
-rw-r--r--app/ui/runview.py53
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}",