App > Put with Files: restore a backup zip onto the connected device
Follows Ontrol's Workbench tool (BPutWithFilesTool / PutApp): the backup's app.sax is encoded for the device's kits and put as app.sab.writing, renamed to app.sab.stage; the platform service's deleteFile clears fonts/* and images/*, then images, fonts (from Sedona home's bank, in the device's cut) and the boot logo go. The zip is checked against the device first: same platform (Orion, Rion or P-ION, from the platform id), and kits the device can load. Falls back to the backup's app.sab, with its kits.scode if needed. - sab_app.encode_sax: .sax -> .sab, ported from Py_sab2sax with Java's value parsing (fromLiteral, nullbool/nullfloat, Integer.decode); byte-identical to sedonac on the saved backups - app_backup.native_image: port of OntOxImageUtil's png -> native .bin - sox.put_file / rename_file - Get with Files: Orion's boot logo is system/logo.jpg - A restart that goes unanswered is taken as the device going down Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -384,7 +384,7 @@ class SoxClient:
|
||||
# file transfer: one at a time; its chunks arrive unasked, so the receiver
|
||||
# hands them to whichever transfer is open
|
||||
self._xfer_lock = threading.Lock()
|
||||
self._xfer = None # _FileGet while a get is open
|
||||
self._xfer = None # _FileGet or _FilePut while a transfer is open
|
||||
|
||||
# ── Connect / Disconnect ────────────────────────────────────
|
||||
|
||||
@@ -989,6 +989,77 @@ class SoxClient:
|
||||
progress(file_size, file_size)
|
||||
return bytes(data)
|
||||
|
||||
def put_file(self, uri, data, progress=None):
|
||||
"""Writes `data` to the device as `uri`.
|
||||
|
||||
Ported from FileTransfer.putFile, as Ontrol's BSoxFilePut does it: an 'f'
|
||||
request opens the transfer, the chunks go out as 'k' messages -- the DASP send
|
||||
window paces them -- and the device answers the last with an unsolicited 'z'
|
||||
once the file is closed. progress(done_bytes, total_bytes) is called from this
|
||||
thread.
|
||||
"""
|
||||
if self.is_closed:
|
||||
raise SoxException("SoxClient closed")
|
||||
with self._xfer_lock:
|
||||
# 21, not FileTransfer's 18: room for Orion's message-length header too.
|
||||
chunk_size = max(16, self._session.ideal_max - 21)
|
||||
req = _prepare_request(ord('f'))
|
||||
req.write_str("p")
|
||||
req.write_str(uri)
|
||||
req.write_i4(len(data))
|
||||
req.write_u2(chunk_size)
|
||||
req.write_u1(0) # end of headers
|
||||
|
||||
xfer = _FilePut()
|
||||
self._xfer = xfer
|
||||
opened = False
|
||||
try:
|
||||
res = self.request(req)
|
||||
res.check_response('F')
|
||||
opened = True
|
||||
file_size = res.i4()
|
||||
chunk_size = res.u2()
|
||||
if file_size != len(data) or chunk_size <= 0:
|
||||
raise SoxException(
|
||||
f"The device opened {uri} for {file_size} bytes in chunks of "
|
||||
f"{chunk_size}; {len(data)} bytes were offered")
|
||||
num_chunks = max(1, -(-file_size // chunk_size))
|
||||
for num in range(num_chunks):
|
||||
if self.is_closed:
|
||||
raise SoxException(f"Session closed while writing {uri}")
|
||||
chunk = data[num * chunk_size:(num + 1) * chunk_size]
|
||||
msg = _prepare_request(ord('k'), 0)
|
||||
msg.write_u2(num)
|
||||
msg.write_u2(len(chunk))
|
||||
msg.write_bytes(chunk)
|
||||
self._session.send(msg.to_bytes())
|
||||
if progress:
|
||||
progress(min(file_size, (num + 1) * chunk_size), file_size)
|
||||
|
||||
timeout = getattr(self._session, "receive_timeout", 30.0) or 30.0
|
||||
deadline = time.monotonic() + timeout
|
||||
while not xfer.closed.wait(0.5):
|
||||
if self.is_closed:
|
||||
raise SoxException(f"Session closed while writing {uri}")
|
||||
if time.monotonic() > deadline:
|
||||
raise SoxException(f"Timed out waiting for the device to close {uri}")
|
||||
opened = False
|
||||
finally:
|
||||
self._xfer = None
|
||||
# Left open, the transfer keeps the device answering "busy" to the next.
|
||||
if opened and not self.is_closed:
|
||||
try:
|
||||
self.request(_prepare_request(ord('z')))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def rename_file(self, from_uri, to_uri):
|
||||
"""Renames a file on the device ('b'), as SoxClient.renameFile."""
|
||||
req = _prepare_request(ord('b'))
|
||||
req.write_str(from_uri)
|
||||
req.write_str(to_uri)
|
||||
self.request(req).check_response('B')
|
||||
|
||||
# ── Networking ──────────────────────────────────────────────
|
||||
|
||||
def request(self, req):
|
||||
@@ -1111,6 +1182,13 @@ class _FileGet:
|
||||
# SoxReceiver — daemon thread
|
||||
# ────────────────────────────────────────────────────────────────
|
||||
|
||||
class _FilePut:
|
||||
"""The put in progress; `closed` is set when the device's 'z' arrives."""
|
||||
|
||||
def __init__(self):
|
||||
self.closed = threading.Event()
|
||||
|
||||
|
||||
class _SoxReceiver(threading.Thread):
|
||||
def __init__(self, client):
|
||||
super().__init__(name="SoxReceiver", daemon=True)
|
||||
@@ -1136,10 +1214,12 @@ class _SoxReceiver(threading.Thread):
|
||||
client._receive_event(msg)
|
||||
elif cmd == ord('k'):
|
||||
xfer = client._xfer
|
||||
if xfer is not None:
|
||||
if isinstance(xfer, _FileGet):
|
||||
xfer.receive_chunk(msg)
|
||||
elif cmd == ord('z'):
|
||||
pass # close of a put -- the editor only gets files
|
||||
xfer = client._xfer # the device closing a put
|
||||
if isinstance(xfer, _FilePut):
|
||||
xfer.closed.set()
|
||||
else:
|
||||
client._receive_response(msg)
|
||||
except DaspException:
|
||||
|
||||
Reference in New Issue
Block a user