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 configparser
|
||||||
import os
|
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")
|
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
|
||||||
DEFAULT_GRID_SIZE = 10
|
DEFAULT_GRID_SIZE = 10
|
||||||
DEFAULT_ADD_TIMESTAMP = True
|
DEFAULT_ADD_TIMESTAMP = True
|
||||||
|
|||||||
+38
-1
@@ -176,6 +176,41 @@ class DeviceSession:
|
|||||||
for cid in parent.children
|
for cid in parent.children
|
||||||
if self.sox_client.cache_get(cid) is not None]
|
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):
|
def find_component_of_type(self, type_name, slot_name):
|
||||||
"""A component of `type_name` with `slot_name` read, loading where services live.
|
"""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")
|
schema_elem = ET.SubElement(sax_root, "schema")
|
||||||
if self.sox_client and self.sox_client.schema_parts:
|
if self.sox_client and self.sox_client.schema_parts:
|
||||||
for part in 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",
|
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")
|
app_elem = ET.SubElement(sax_root, "app")
|
||||||
if root_comp is not None:
|
if root_comp is not None:
|
||||||
|
|||||||
+9
-4
@@ -82,6 +82,11 @@ def column_label(version, checksum):
|
|||||||
return version if version else 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):
|
def action_options(kit_name, current_checksum, is_declared, builds):
|
||||||
"""The Action dropdown for one kit: (label, checksum to apply) pairs, no-op first.
|
"""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
|
installed_version = None
|
||||||
for version, checksum in builds:
|
for version, checksum in builds:
|
||||||
if checksum == current_checksum:
|
if same_checksum(checksum, current_checksum):
|
||||||
installed_version = version
|
installed_version = version
|
||||||
break
|
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
|
installed_rank = palette.version_sort_key(installed_version) if installed_version else None
|
||||||
|
|
||||||
for version, checksum in builds:
|
for version, checksum in builds:
|
||||||
if is_declared and checksum == current_checksum:
|
if is_declared and same_checksum(checksum, current_checksum):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
label = build_label(version, checksum)
|
label = build_label(version, checksum)
|
||||||
@@ -176,7 +181,7 @@ def collect_rows():
|
|||||||
installed_text = NO_CHECKSUM
|
installed_text = NO_CHECKSUM
|
||||||
else:
|
else:
|
||||||
installed_text = column_label(
|
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({
|
rows.append({
|
||||||
@@ -512,7 +517,7 @@ class KitsPanel:
|
|||||||
if "section" in row or not row["declared"] or not row["latest"]:
|
if "section" in row or not row["declared"] or not row["latest"]:
|
||||||
continue
|
continue
|
||||||
latest_checksum = row["latest"][1]
|
latest_checksum = row["latest"][1]
|
||||||
if latest_checksum != row["checksum"]:
|
if not same_checksum(latest_checksum, row["checksum"]):
|
||||||
selections.append((row["kit"], latest_checksum))
|
selections.append((row["kit"], latest_checksum))
|
||||||
|
|
||||||
if selections:
|
if selections:
|
||||||
|
|||||||
+12
@@ -501,6 +501,18 @@ def used_kit_names():
|
|||||||
if app_state.xml_root_element is None:
|
if app_state.xml_root_element is None:
|
||||||
return names
|
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"):
|
for comp in app_state.xml_root_element.iter("comp"):
|
||||||
type_str = comp.get("type") or ""
|
type_str = comp.get("type") or ""
|
||||||
if "::" in type_str:
|
if "::" in type_str:
|
||||||
|
|||||||
Reference in New Issue
Block a user