diff --git a/tui/screens/backup.py b/tui/screens/backup.py index d36bcfa..b842743 100644 --- a/tui/screens/backup.py +++ b/tui/screens/backup.py @@ -57,19 +57,26 @@ class BackupScreen(Screen): msg += f"\nRemote: {remote}" self.app.push_screen( ConfirmDialog(msg, "Confirm Backup"), - callback=lambda ok: self._do_backup(target, remote) if ok else None, + callback=lambda ok: self._confirmed_backup(target, remote) if ok else None, ) elif event.button.id == "btn-backup-all": self.app.push_screen( ConfirmDialog("Backup ALL targets now?", "Confirm Backup"), - callback=lambda ok: self._do_backup_all() if ok else None, + callback=lambda ok: self._confirmed_backup_all() if ok else None, ) - @work - async def _do_backup(self, target: str, remote: str) -> None: + def _confirmed_backup(self, target: str, remote: str) -> None: log_screen = OperationLog(f"Backup: {target}") self.app.push_screen(log_screen) - await log_screen.wait_ready() + self._run_backup(log_screen, target, remote) + + def _confirmed_backup_all(self) -> None: + log_screen = OperationLog("Backup All Targets") + self.app.push_screen(log_screen) + self._run_backup_all(log_screen) + + @work + async def _run_backup(self, log_screen: OperationLog, target: str, remote: str) -> None: args = ["backup", f"--target={target}"] if remote: args.append(f"--remote={remote}") @@ -81,10 +88,7 @@ class BackupScreen(Screen): log_screen.finish() @work - async def _do_backup_all(self) -> None: - log_screen = OperationLog("Backup All Targets") - self.app.push_screen(log_screen) - await log_screen.wait_ready() + async def _run_backup_all(self, log_screen: OperationLog) -> None: rc = await stream_cli(log_screen.write, "backup", "--all") if rc == 0: log_screen.write("\n[green]All backups completed.[/green]") diff --git a/tui/screens/remotes.py b/tui/screens/remotes.py index 15da545..4703d2e 100644 --- a/tui/screens/remotes.py +++ b/tui/screens/remotes.py @@ -81,11 +81,13 @@ class RemotesScreen(Screen): else: self.notify("Select a remote first", severity="warning") - @work - async def _test_remote(self, name: str) -> None: + def _test_remote(self, name: str) -> None: log_screen = OperationLog(f"Testing Remote: {name}") self.app.push_screen(log_screen) - await log_screen.wait_ready() + self._run_test_remote(log_screen, name) + + @work + async def _run_test_remote(self, log_screen: OperationLog, name: str) -> None: rc, stdout, stderr = await run_cli("remotes", "test", f"--name={name}") if stdout: log_screen.write(stdout) diff --git a/tui/screens/restore.py b/tui/screens/restore.py index 47e2c88..586d44a 100644 --- a/tui/screens/restore.py +++ b/tui/screens/restore.py @@ -107,14 +107,16 @@ class RestoreScreen(Screen): msg += "\nLocation: In-place" self.app.push_screen( ConfirmDialog(msg, "Confirm Restore"), - callback=lambda ok: self._do_restore(target, remote, snapshot, dest) if ok else None, + callback=lambda ok: self._confirmed_restore(target, remote, snapshot, dest) if ok else None, ) - @work - async def _do_restore(self, target: str, remote: str, snapshot: str, dest: str) -> None: + def _confirmed_restore(self, target: str, remote: str, snapshot: str, dest: str) -> None: log_screen = OperationLog(f"Restore: {target}") self.app.push_screen(log_screen) - await log_screen.wait_ready() + self._run_restore(log_screen, target, remote, snapshot, dest) + + @work + async def _run_restore(self, log_screen: OperationLog, target: str, remote: str, snapshot: str, dest: str) -> None: args = ["restore", f"--target={target}", f"--remote={remote}", f"--snapshot={snapshot}"] if dest: args.append(f"--dest={dest}") diff --git a/tui/screens/retention.py b/tui/screens/retention.py index 24d1cce..9d4da7b 100644 --- a/tui/screens/retention.py +++ b/tui/screens/retention.py @@ -51,12 +51,12 @@ class RetentionScreen(Screen): target = str(target_sel.value) self.app.push_screen( ConfirmDialog(f"Run retention cleanup for '{target}'?", "Confirm"), - callback=lambda ok: self._do_cleanup(target) if ok else None, + callback=lambda ok: self._confirmed_cleanup(target) if ok else None, ) elif event.button.id == "btn-cleanup-all": self.app.push_screen( ConfirmDialog("Run retention cleanup for ALL targets?", "Confirm"), - callback=lambda ok: self._do_cleanup_all() if ok else None, + callback=lambda ok: self._confirmed_cleanup_all() if ok else None, ) elif event.button.id == "btn-save-count": val = self.query_one("#ret-count", Input).value.strip() @@ -66,11 +66,18 @@ class RetentionScreen(Screen): update_conf_key(CONFIG_DIR / "gniza.conf", "RETENTION_COUNT", val) self.notify(f"Retention count set to {val}.") - @work - async def _do_cleanup(self, target: str) -> None: + def _confirmed_cleanup(self, target: str) -> None: log_screen = OperationLog(f"Retention: {target}") self.app.push_screen(log_screen) - await log_screen.wait_ready() + self._run_cleanup(log_screen, target) + + def _confirmed_cleanup_all(self) -> None: + log_screen = OperationLog("Retention: All Targets") + self.app.push_screen(log_screen) + self._run_cleanup_all(log_screen) + + @work + async def _run_cleanup(self, log_screen: OperationLog, target: str) -> None: rc = await stream_cli(log_screen.write, "retention", f"--target={target}") if rc == 0: log_screen.write("\n[green]Cleanup completed.[/green]") @@ -79,10 +86,7 @@ class RetentionScreen(Screen): log_screen.finish() @work - async def _do_cleanup_all(self) -> None: - log_screen = OperationLog("Retention: All Targets") - self.app.push_screen(log_screen) - await log_screen.wait_ready() + async def _run_cleanup_all(self, log_screen: OperationLog) -> None: rc = await stream_cli(log_screen.write, "retention", "--all") if rc == 0: log_screen.write("\n[green]All cleanups completed.[/green]") diff --git a/tui/screens/schedule.py b/tui/screens/schedule.py index af43063..cd2a345 100644 --- a/tui/screens/schedule.py +++ b/tui/screens/schedule.py @@ -216,11 +216,13 @@ class ScheduleScreen(Screen): self.notify(f"Schedule '{name}' deleted.") self._refresh_table() - @work - async def _install_schedules(self) -> None: + def _install_schedules(self) -> None: log_screen = OperationLog("Install Schedules") self.app.push_screen(log_screen) - await log_screen.wait_ready() + self._run_install(log_screen) + + @work + async def _run_install(self, log_screen: OperationLog) -> None: rc, stdout, stderr = await run_cli("schedule", "install") if stdout: log_screen.write(stdout) @@ -228,11 +230,13 @@ class ScheduleScreen(Screen): log_screen.write(stderr) log_screen.finish() - @work - async def _remove_schedules(self) -> None: + def _remove_schedules(self) -> None: log_screen = OperationLog("Remove Schedules") self.app.push_screen(log_screen) - await log_screen.wait_ready() + self._run_remove(log_screen) + + @work + async def _run_remove(self, log_screen: OperationLog) -> None: rc, stdout, stderr = await run_cli("schedule", "remove") if stdout: log_screen.write(stdout) @@ -240,11 +244,13 @@ class ScheduleScreen(Screen): log_screen.write(stderr) log_screen.finish() - @work - async def _show_crontab(self) -> None: + def _show_crontab(self) -> None: log_screen = OperationLog("Current Crontab") self.app.push_screen(log_screen) - await log_screen.wait_ready() + self._run_show(log_screen) + + @work + async def _run_show(self, log_screen: OperationLog) -> None: rc, stdout, stderr = await run_cli("schedule", "show") if stdout: log_screen.write(stdout) diff --git a/tui/widgets/operation_log.py b/tui/widgets/operation_log.py index c5f09cb..1f0c33f 100644 --- a/tui/widgets/operation_log.py +++ b/tui/widgets/operation_log.py @@ -3,7 +3,7 @@ import asyncio from rich.text import Text from textual.app import ComposeResult from textual.screen import ModalScreen -from textual.widgets import RichLog, Button, Static, LoadingIndicator +from textual.widgets import RichLog, Button, Static from textual.containers import Vertical, Horizontal @@ -14,26 +14,16 @@ class OperationLog(ModalScreen[None]): def __init__(self, title: str = "Operation Output"): super().__init__() self._title = title - self._mounted_event = asyncio.Event() - self._buffer: list[str] = [] self._running = True def compose(self) -> ComposeResult: with Vertical(id="op-log"): with Horizontal(id="ol-header"): yield Static(self._title, id="ol-title") - yield LoadingIndicator(id="ol-spinner") + yield Static("⏳", id="ol-spinner") yield RichLog(id="ol-log", wrap=True, highlight=True, markup=True) yield Button("Close", variant="primary", id="ol-close") - def on_mount(self) -> None: - # Flush any buffered writes - log = self.query_one("#ol-log", RichLog) - for text in self._buffer: - self._write_to_log(log, text) - self._buffer.clear() - self._mounted_event.set() - def on_button_pressed(self, event: Button.Pressed) -> None: self.dismiss(None) @@ -46,22 +36,16 @@ class OperationLog(ModalScreen[None]): else: log.write(text) - async def wait_ready(self) -> None: - await self._mounted_event.wait() - def finish(self) -> None: self._running = False try: - self.query_one("#ol-spinner", LoadingIndicator).display = False + self.query_one("#ol-spinner", Static).update("✅") except Exception: pass def write(self, text: str) -> None: - if not self._mounted_event.is_set(): - self._buffer.append(text) - return try: log = self.query_one("#ol-log", RichLog) self._write_to_log(log, text) except Exception: - self._buffer.append(text) + pass