Add App > Get with File: full app backup zip from the device

Reads app.sab and kits.scode over Sox file transfer, decodes app.sab to
app.sax (byte-identical to sedonac), and on Orion/Rion also fetches the
images and fonts the app names, with their sources from Sedona home's
banks. Offers to save the app on the device first when it is dirty, and
asks where to save with <Sedona home>/store/apps and
app-<device>-<app>-<timestamp>.zip filled in.

DASP trace lines are now printed by their own thread: printing from the
network threads to an unread stdout pipe blocked the receiver and made
file transfers time out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 15:25:05 +03:00
co-authored by Claude Opus 5.5
parent 9b1d029b14
commit 894fdb7eb5
6 changed files with 970 additions and 10 deletions
+50 -1
View File
@@ -98,7 +98,56 @@ def _trace(direction, msg, datagram=None):
hexdump = ""
if datagram:
hexdump = " [" + datagram.hex(" ") + "]"
print(f"[DASP] {ts} {direction} {name}{seq}{ack} payload={plen}{hexdump}")
_trace_out(f"[DASP] {ts} {direction} {name}{seq}{ack} payload={plen}{hexdump}")
# Trace lines are printed by a thread of their own. The send and receive threads must
# never wait on the console: when stdout is a pipe nobody reads, print blocks once the
# pipe fills, and a file transfer's burst of chunks filled it in seconds -- the
# receiver stopped and every request timed out. A full queue drops lines instead.
_trace_queue = queue.Queue(maxsize=5000)
_trace_thread = None
_trace_dropped = 0
def _trace_out(line):
global _trace_thread, _trace_dropped
if _trace_thread is None:
_trace_thread = threading.Thread(target=_trace_writer, name="DaspTrace", daemon=True)
_trace_thread.start()
try:
_trace_queue.put_nowait(line)
except queue.Full:
_trace_dropped += 1
def _trace_writer():
global _trace_dropped
while True:
line = _trace_queue.get()
if _trace_dropped:
dropped, _trace_dropped = _trace_dropped, 0
line = f"[DASP] ... {dropped} trace lines dropped (console not keeping up)\n{line}"
_write_stdout(line + "\n")
def _write_stdout(text):
"""Writes to the process's stdout handle directly, past Python's buffered writer.
The buffered writer has a lock, and a daemon thread holding it while the
interpreter shuts down aborts the process ("could not acquire lock for stdout").
"""
import sys
stream = sys.__stdout__
if stream is None: # pythonw: no console to write to
return
try:
fd = stream.fileno()
data = text.encode(stream.encoding or "utf-8", errors="replace")
while data:
data = data[os.write(fd, data):]
except (OSError, ValueError):
pass
_ERROR_HINTS = {