mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 04:04:21 +02:00
Devices: fixes from review round 6
- The headset a change is meant for is checked and the work counted in one step, so a switch can't slip in between (uploads too). - A sideloaded title read on one headset can't be installed on another; open confirmations close on a switch. - The only headset can't be removed while its ssh alias stays behind. - Answers about the previous headset are dropped without touching panels; the catalogue's Installed tags are rebuilt for the new headset. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
41ac28248a
commit
ba33d2ff40
4 files changed
+35
-12
No files matched your search
+13
-9
@@ -106,8 +106,13 @@ _work = [0]
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def working():
|
||||
def working(meant=None):
|
||||
"""Counts as running work. `meant`: the headset the page made this change for; if the
|
||||
app has switched away from it, refuse (checked together with counting, so a switch
|
||||
can't slip in between)."""
|
||||
with _work_lock:
|
||||
if LINK and meant and meant != LINK.active_device()["id"]:
|
||||
raise Failure("Frame Control switched headsets; try again on this one", 409)
|
||||
_work[0] += 1
|
||||
try:
|
||||
yield
|
||||
@@ -689,7 +694,8 @@ def stage_title(path, temp_dir=None, name=None):
|
||||
raise
|
||||
token = secrets.token_hex(12)
|
||||
with _titles_lock:
|
||||
_staged[token] = {"plan": plan, "dir": temp_dir, "time": time.time()}
|
||||
_staged[token] = {"plan": plan, "dir": temp_dir, "time": time.time(),
|
||||
"device": LINK.active_device()["id"] if LINK else None}
|
||||
return {"message": f"Read {plan['source']}: {plan['target']} with {plan['runtime_label']}",
|
||||
"token": token, "plan": frame_titles.public(plan)}
|
||||
|
||||
@@ -730,6 +736,9 @@ def titles(body):
|
||||
if action == "discard":
|
||||
_drop_staged(entry)
|
||||
return {"message": "Discarded"}
|
||||
if LINK and entry.get("device") != LINK.active_device()["id"]:
|
||||
_drop_staged(entry) # it was checked against the other headset's titles
|
||||
raise Failure("Frame Control switched headsets since this was read; drop the file again", 409)
|
||||
with _titles_lock:
|
||||
_title_jobs[token] = {"stage": "Starting", "fraction": 0, "done": False, "error": None,
|
||||
"message": None, "title": None, "time": time.time()}
|
||||
@@ -1468,15 +1477,10 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if not self.local_request():
|
||||
return
|
||||
path = urlparse(self.path).path
|
||||
# A change the page made for a headset the app has since switched away from
|
||||
# (its buttons were still showing): refuse it rather than do it to this one.
|
||||
meant = self.headers.get("X-Frame-Device")
|
||||
if LINK and meant and path != "/api/devices" and meant != LINK.active_device()["id"]:
|
||||
self.send_json({"error": "Frame Control switched headsets; try again on this one"}, 409)
|
||||
return
|
||||
try:
|
||||
if path == "/api/upload":
|
||||
with working():
|
||||
with working(meant):
|
||||
self.send_json(self.upload())
|
||||
return
|
||||
handler = POST.get(path)
|
||||
@@ -1489,7 +1493,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
body = json.loads(self.rfile.read(length) or b"{}")
|
||||
if not isinstance(body, dict):
|
||||
raise Failure("request body must be a JSON object", 400)
|
||||
with (contextlib.nullcontext() if path == "/api/devices" else working()):
|
||||
with (contextlib.nullcontext() if path == "/api/devices" else working(meant)):
|
||||
result = handler(body)
|
||||
self.send_json(result)
|
||||
except Failure as e:
|
||||
|
||||
Reference in new issue
Block a user