Files
b0hemia e0bc4785e9 diskOS installer: initial public beta
Flashes the diskOS custom UI onto the FiiO Snowsky Disc over Ingenic mask-ROM USB,
building the image from your own stock firmware. Runs from source via install.sh.
2026-08-26 15:26:14 +10:00

138 lines
4.1 KiB
Python

"""Reporter interface - decouples the engine (imagebuild/flasher/state) from the
front-end. The engine reports FACTS; it never touches widgets or prints directly.
CLIReporter -> renders to the terminal via ui.py
QueueReporter -> enqueues immutable events for the Tk GUI to drain on its main
thread (the engine runs on a worker thread)
Contract:
phase(name, destructive=False) a new stage began
status(message) transient 'what's happening now' line
log(line) detail/log line
progress(completed, total) determinate progress (total > 0)
indeterminate(active, note="") long op with no count (the flash) on/off
warning(message) / ok(message) / error(message)
"""
import threading
from . import ui
class Reporter:
def phase(self, name, destructive=False): ...
def status(self, message): ...
def log(self, line): ...
def progress(self, completed, total): ...
def indeterminate(self, active, note="", expect_secs=None): ...
def warning(self, message): ...
def ok(self, message): ...
def error(self, message): ...
class CLIReporter(Reporter):
"""Terminal renderer - preserves the existing CLI look via ui.py."""
def __init__(self):
self._phase = ""
self._bar = None
self._hb = None
self._hb_stop = None
self._hb_thread = None
def phase(self, name, destructive=False):
self._end_bar()
self._phase = name
ui.step(name + (" (this rewrites the device)" if destructive else ""))
def status(self, message):
self._end_bar()
ui.info(message)
def log(self, line):
ui.info(ui.dim(line))
def progress(self, completed, total):
if not total or total <= 0:
return
if self._bar is None:
self._bar = ui.Bar(self._phase or "working", total)
self._bar.update(completed)
if completed >= total:
self._bar.done()
self._bar = None
def _end_bar(self):
if self._bar is not None:
self._bar.done()
self._bar = None
def indeterminate(self, active, note="", expect_secs=None):
if active:
if self._hb is not None:
return
self._hb = ui.Heartbeat(note or self._phase or "working", expect_secs=expect_secs)
self._hb_stop = threading.Event()
def _run(hb, stop):
while not stop.is_set():
hb.tick()
stop.wait(0.5)
hb.stop()
self._hb_thread = threading.Thread(target=_run, args=(self._hb, self._hb_stop), daemon=True)
self._hb_thread.start()
else:
if self._hb_stop:
self._hb_stop.set()
if self._hb_thread:
self._hb_thread.join(timeout=2)
self._hb = self._hb_stop = self._hb_thread = None
def warning(self, message):
self._end_bar()
ui.warn(message)
def ok(self, message):
self._end_bar()
ui.ok(message)
def error(self, message):
self._end_bar()
ui.err(message)
class QueueReporter(Reporter):
"""Enqueues immutable (kind, payload) events for the GUI to drain via
root.after on the main thread. NEVER touches Tk widgets itself."""
def __init__(self, q):
self.q = q
def _emit(self, kind, **kw):
self.q.put((kind, kw))
def phase(self, name, destructive=False):
self._emit("phase", name=name, destructive=destructive)
def status(self, message):
self._emit("status", message=message)
def log(self, line):
self._emit("log", line=line)
def progress(self, completed, total):
self._emit("progress", completed=completed, total=total)
def indeterminate(self, active, note="", expect_secs=None):
self._emit("indeterminate", active=active, note=note, expect_secs=expect_secs)
def warning(self, message):
self._emit("warning", message=message)
def ok(self, message):
self._emit("ok", message=message)
def error(self, message):
self._emit("error", message=message)