Fix Manage Kits for apps read from a device
The schema built from a Sox session spelled checksums as signed decimals, so no kit matched a manifest: Installed showed raw checksums and Action read "Keep at checksum ...". They are now 8 hex digits like a .sax file, and checksums compare regardless of zero-padding. A device session only loads folders as they are opened, so only sys counted as used. Used kits now come from the whole component tree, read one request at a time so a small controller is not flooded into dropping the session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it.
|
||||
import configparser
|
||||
import os
|
||||
|
||||
VERSION = "0.0.0.137"
|
||||
VERSION = "0.0.0.138"
|
||||
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
|
||||
DEFAULT_GRID_SIZE = 10
|
||||
DEFAULT_ADD_TIMESTAMP = True
|
||||
|
||||
+38
-1
@@ -176,6 +176,41 @@ class DeviceSession:
|
||||
for cid in parent.children
|
||||
if self.sox_client.cache_get(cid) is not None]
|
||||
|
||||
def used_kit_names(self):
|
||||
"""Every kit a component on the device belongs to, loading the whole tree to see.
|
||||
|
||||
The DOM only holds what has been opened, so it cannot answer this on its own. Only
|
||||
the tree is read — no config or runtime.
|
||||
|
||||
Requests go one at a time. A small controller handles a single folder's burst fine,
|
||||
but a back-to-back stream of them fills its receive buffer; it stops acking, and
|
||||
DASP closes the whole session ("unacked send timeout"). Waiting for each reply
|
||||
before sending the next keeps it at the pace the device sets. Anything already
|
||||
cached costs nothing, so only the first call is slow.
|
||||
"""
|
||||
client = self.sox_client
|
||||
if client is None:
|
||||
return set()
|
||||
|
||||
pending = [0]
|
||||
loaded = 0
|
||||
while pending:
|
||||
comp_id = pending.pop()
|
||||
comp = client.cache_get(comp_id)
|
||||
if comp is None:
|
||||
comp = client.load_ids([comp_id], checked=False)[0]
|
||||
loaded += 1
|
||||
if loaded % 20 == 0:
|
||||
app_state.set_status(f"Reading components from the device… {loaded}")
|
||||
if app_state.status_label is not None:
|
||||
app_state.status_label.update_idletasks()
|
||||
if comp is not None:
|
||||
pending.extend(comp.children)
|
||||
|
||||
parts = client.schema_parts or []
|
||||
return {parts[c.kit_id].name for c in client.cache
|
||||
if c is not None and 0 <= c.kit_id < len(parts)}
|
||||
|
||||
def find_component_of_type(self, type_name, slot_name):
|
||||
"""A component of `type_name` with `slot_name` read, loading where services live.
|
||||
|
||||
@@ -294,8 +329,10 @@ class DeviceSession:
|
||||
schema_elem = ET.SubElement(sax_root, "schema")
|
||||
if self.sox_client and self.sox_client.schema_parts:
|
||||
for part in self.sox_client.schema_parts:
|
||||
# Sox sends the checksum as a signed int; .sax files and manifest names
|
||||
# spell it as 8 hex digits, and nothing matches unless this does too.
|
||||
ET.SubElement(schema_elem, "kit",
|
||||
name=part.name, checksum=str(part.checksum))
|
||||
name=part.name, checksum=f"{part.checksum & 0xFFFFFFFF:08x}")
|
||||
|
||||
app_elem = ET.SubElement(sax_root, "app")
|
||||
if root_comp is not None:
|
||||
|
||||
+9
-4
@@ -82,6 +82,11 @@ def column_label(version, checksum):
|
||||
return version if version else checksum
|
||||
|
||||
|
||||
def same_checksum(a, b):
|
||||
"""Whether two checksums name the same build, whatever their zero-padding."""
|
||||
return bool(a) and bool(b) and a.lower().lstrip("0") == b.lower().lstrip("0")
|
||||
|
||||
|
||||
def action_options(kit_name, current_checksum, is_declared, builds):
|
||||
"""The Action dropdown for one kit: (label, checksum to apply) pairs, no-op first.
|
||||
|
||||
@@ -91,7 +96,7 @@ def action_options(kit_name, current_checksum, is_declared, builds):
|
||||
"""
|
||||
installed_version = None
|
||||
for version, checksum in builds:
|
||||
if checksum == current_checksum:
|
||||
if same_checksum(checksum, current_checksum):
|
||||
installed_version = version
|
||||
break
|
||||
|
||||
@@ -109,7 +114,7 @@ def action_options(kit_name, current_checksum, is_declared, builds):
|
||||
installed_rank = palette.version_sort_key(installed_version) if installed_version else None
|
||||
|
||||
for version, checksum in builds:
|
||||
if is_declared and checksum == current_checksum:
|
||||
if is_declared and same_checksum(checksum, current_checksum):
|
||||
continue
|
||||
|
||||
label = build_label(version, checksum)
|
||||
@@ -176,7 +181,7 @@ def collect_rows():
|
||||
installed_text = NO_CHECKSUM
|
||||
else:
|
||||
installed_text = column_label(
|
||||
next((v for v, c in builds if c == checksum), ""), checksum
|
||||
next((v for v, c in builds if same_checksum(c, checksum)), ""), checksum
|
||||
)
|
||||
|
||||
rows.append({
|
||||
@@ -512,7 +517,7 @@ class KitsPanel:
|
||||
if "section" in row or not row["declared"] or not row["latest"]:
|
||||
continue
|
||||
latest_checksum = row["latest"][1]
|
||||
if latest_checksum != row["checksum"]:
|
||||
if not same_checksum(latest_checksum, row["checksum"]):
|
||||
selections.append((row["kit"], latest_checksum))
|
||||
|
||||
if selections:
|
||||
|
||||
+12
@@ -501,6 +501,18 @@ def used_kit_names():
|
||||
if app_state.xml_root_element is None:
|
||||
return names
|
||||
|
||||
# A device session's DOM only holds the folders opened so far.
|
||||
session = app_state.device_session
|
||||
if session is not None and session.is_connected:
|
||||
try:
|
||||
return session.used_kit_names()
|
||||
except Exception as error:
|
||||
messagebox.showwarning(
|
||||
"Kits",
|
||||
f"Could not read every component from the device:\n{error}\n\n"
|
||||
"Kits shown as used come only from the folders opened so far.",
|
||||
)
|
||||
|
||||
for comp in app_state.xml_root_element.iter("comp"):
|
||||
type_str = comp.get("type") or ""
|
||||
if "::" in type_str:
|
||||
|
||||
Reference in New Issue
Block a user