mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 17:00:41 +02:00
Devices: fixes from review round 17
- A command that fails after a switch doesn't make the connector drop the new headset's connection. - On first import, the app keeps using the `frame` headset even when Set Up Connection put another block above it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
fa05a4b310
commit
90fd2684ba
5 files changed
+35
-3
No files matched your search
@@ -113,6 +113,17 @@ class Migration(Base):
|
||||
self.assertEqual(len(data["devices"]), 2)
|
||||
self.assertEqual(fd.Registry(self.dir / "devices.json").devices(), self.reg.devices())
|
||||
|
||||
def test_first_import_keeps_using_frame(self):
|
||||
# Set Up Connection puts each new block first; the app used `frame` before.
|
||||
blocks = CONFIG.split("# >>> steam-frame (frame-2) >>>")
|
||||
head, first = blocks[0].split("# >>> steam-frame (frame) >>>")
|
||||
second, tail = blocks[1].split("# <<< steam-frame (frame-2) <<<")
|
||||
(self.ssh / "config").write_text(head + "# >>> steam-frame (frame-2) >>>" + second + "# <<< steam-frame (frame-2) <<<\n"
|
||||
+ "# >>> steam-frame (frame) >>>" + first + tail)
|
||||
self.reg.sync_from_config(seed=False)
|
||||
self.assertEqual([d["alias"] for d in self.reg.devices()], ["frame-2", "frame"])
|
||||
self.assertEqual(self.reg.active(), self.reg.by_alias("frame")["id"])
|
||||
|
||||
def test_setup_finding_a_new_address_adds_it(self):
|
||||
self.reg.sync_from_config(seed=False)
|
||||
(self.ssh / "config").write_text(CONFIG.replace("HostName frame.tail1234.ts.net", "HostName 192.168.1.237"))
|
||||
|
||||
@@ -304,6 +304,14 @@ class Connecting(unittest.TestCase):
|
||||
self.assertIn("User deck", cfg.read_text()) # changed meanwhile: left as it is
|
||||
self.assertIn("left as it is", out["message"])
|
||||
|
||||
def test_a_late_failure_from_the_last_headset_is_ignored(self):
|
||||
self.link.state["phase"] = "connected"
|
||||
self.link.gen = 3
|
||||
self.link.lost("ssh: connect to host a port 22: Operation timed out", 2) # sent before the switch
|
||||
self.assertEqual(self.link.kicks, [])
|
||||
self.link.lost("ssh: connect to host b port 22: Operation timed out", 3)
|
||||
self.assertEqual(len(self.link.kicks), 1)
|
||||
|
||||
def test_probes_from_an_earlier_attempt_leave_the_new_rows_alone(self):
|
||||
self.link.state.update(attempt=2, probes=[{"host": "b", "state": "waiting"}])
|
||||
self.link.probe_update(0, 1, state="answered", ip="10.0.0.2")
|
||||
|
||||
@@ -672,6 +672,7 @@ class Registry:
|
||||
blocks = parse_blocks(read_config(self.config))
|
||||
changed = False
|
||||
with self.lock:
|
||||
first = not self.data["devices"] and not self.data.get("active")
|
||||
for b in blocks:
|
||||
host = b["hostname"] if b["hostname"] and HOST_RE.fullmatch(b["hostname"]) else None
|
||||
user = b["user"] if b["user"] and NAME_RE.fullmatch(b["user"]) else DEFAULT_USER
|
||||
@@ -714,6 +715,13 @@ class Registry:
|
||||
aliases = {b["alias"] for b in blocks}
|
||||
for d in self.data["devices"]:
|
||||
d["managed"] = d["alias"] in aliases
|
||||
if first:
|
||||
# First import: the headset the app used before is `frame`, even if Set Up
|
||||
# Connection put another block above it.
|
||||
frame = next((d for d in self.data["devices"] if d["alias"] == "frame"), None)
|
||||
if frame and self.data.get("active") != frame["id"]:
|
||||
self.data["active"] = frame["id"]
|
||||
changed = True
|
||||
if changed:
|
||||
self.save()
|
||||
return changed
|
||||
+6
-2
@@ -325,8 +325,12 @@ class Link:
|
||||
def route_key(device):
|
||||
return device["id"], device.get("user"), device.get("port"), tuple(device.get("frozen") or ())
|
||||
|
||||
def lost(self, message):
|
||||
"""A command couldn't reach the headset (Windows has no master to watch)."""
|
||||
def lost(self, message, gen=None):
|
||||
"""A command couldn't reach the headset (Windows has no master to watch). `gen`:
|
||||
the route it was sent on; one to a headset since switched away from says nothing
|
||||
about this one."""
|
||||
if gen is not None and gen != self.gen:
|
||||
return
|
||||
if self.state["phase"] == "connected":
|
||||
self.kick(f"lost: {message}")
|
||||
|
||||
|
||||
+2
-1
@@ -254,6 +254,7 @@ def ensure_master():
|
||||
|
||||
|
||||
def ssh(remote, *, stdin=None, timeout=30, text=True):
|
||||
route_gen = LINK.gen if LINK else None # which headset this command is for
|
||||
try:
|
||||
ensure_master()
|
||||
# Never let ssh inherit our stdin: under the app it's the pipe held open for
|
||||
@@ -266,7 +267,7 @@ def ssh(remote, *, stdin=None, timeout=30, text=True):
|
||||
if r.returncode != 0:
|
||||
err = (r.stderr or r.stdout) if text else (r.stderr or r.stdout).decode(errors="replace")
|
||||
if r.returncode == 255 and LINK and unreachable(err):
|
||||
LINK.lost(err) # ssh itself failed: the connector reconnects
|
||||
LINK.lost(err, route_gen) # ssh itself failed: the connector reconnects
|
||||
failure = Failure(strip_ansi(err).strip() or f"ssh exited {r.returncode}")
|
||||
failure.stdout = r.stdout if text else r.stdout.decode(errors="replace")
|
||||
raise failure
|
||||
|
||||
Reference in new issue
Block a user