mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 18:47:04 +01:00
Several sessions can now share one phone: a lease covers one package (the real screen stays the default scope, so existing callers keep their meaning), and only phone-wide actions take `--whole-phone`, which is capped at 300 s and runs its `--cleanup` commands exactly once on release or, via a detached reaper, on expiry. The serial is always resolved, never the "default" placeholder that let android_ui and phone_deploy lease different keys for the same phone (2026-09-25). Adds status --json, holds and whoami for phone_vd.sh and the raw-adb hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Ajzz98xksbfak8CaunZWC
104 lines
3.2 KiB
Python
104 lines
3.2 KiB
Python
"""Release, status, the ``holds`` check the adb hook asks, and the reaper."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from typing import TYPE_CHECKING
|
|
|
|
from python_pkg.phone_lease import _cleanup
|
|
from python_pkg.phone_lease.lease import (
|
|
SCREEN,
|
|
WHOLE_PHONE,
|
|
Clock,
|
|
Lease,
|
|
lease_paths,
|
|
locked_update,
|
|
read_leases,
|
|
)
|
|
from python_pkg.phone_lease.owner import owner_id
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
# Longest the reaper sleeps between looks at the lease file.
|
|
REAP_POLL = 5.0
|
|
|
|
|
|
def release(
|
|
serial: str, *, scope: str = f"pkg:{SCREEN}", owner: str | None = None
|
|
) -> bool:
|
|
"""Drop ``owner``'s lease on ``scope``, running its cleanup. True if dropped."""
|
|
me = owner_id() if owner is None else owner
|
|
dropped: list[Lease] = []
|
|
|
|
def drop(live: list[Lease], _now: float) -> list[Lease]:
|
|
dropped.extend(x for x in live if (x.owner, x.scope) == (me, scope))
|
|
return [x for x in live if x not in dropped]
|
|
|
|
locked_update(serial, drop, time.time())
|
|
for lease in dropped:
|
|
_cleanup.run(lease.cleanup)
|
|
return bool(dropped)
|
|
|
|
|
|
def status_all(serial: str, now: float | None = None) -> list[Lease]:
|
|
"""Every live lease on ``serial`` (read-only: no cleanup runs here)."""
|
|
at = time.time() if now is None else now
|
|
return [x for x in read_leases(lease_paths(serial)[1]) if not x.is_expired(at)]
|
|
|
|
|
|
def status(
|
|
serial: str, now: float | None = None, *, scope: str = f"pkg:{SCREEN}"
|
|
) -> Lease | None:
|
|
"""The live lease on ``scope``, or None when free."""
|
|
return next((x for x in status_all(serial, now) if x.scope == scope), None)
|
|
|
|
|
|
def holds(
|
|
serial: str, owner: str, scope: str = f"pkg:{SCREEN}", now: float | None = None
|
|
) -> bool:
|
|
"""Whether ``owner`` may act on ``scope`` right now.
|
|
|
|
True when it holds that scope or the whole phone. This is the question
|
|
the raw-adb hook asks before letting an ``input``/``am`` through.
|
|
"""
|
|
return any(
|
|
x.owner == owner and x.scope in {scope, WHOLE_PHONE}
|
|
for x in status_all(serial, now)
|
|
)
|
|
|
|
|
|
def reap(
|
|
serial: str,
|
|
owner: str,
|
|
scope: str,
|
|
*,
|
|
clock: Clock | None = None,
|
|
alive: Callable[[], bool] = lambda: True,
|
|
) -> bool:
|
|
"""Wait until ``owner``'s lease on ``scope`` ends; run its cleanup if it expired.
|
|
|
|
Refreshes push the expiry out, so this re-reads the file after every
|
|
sleep. Returns True when it ran the cleanup, False when the holder
|
|
released first (``release`` already ran it) or ``alive`` says stop.
|
|
"""
|
|
clock = clock or Clock()
|
|
while alive():
|
|
lease = next(
|
|
(
|
|
x
|
|
for x in read_leases(lease_paths(serial)[1])
|
|
if (x.owner, x.scope) == (owner, scope)
|
|
),
|
|
None,
|
|
)
|
|
if lease is None:
|
|
return False
|
|
if lease.is_expired(clock.now()):
|
|
# locked_update drops expired leases and runs their cleanup itself.
|
|
locked_update(serial, lambda live, _now: live, clock.now())
|
|
return True
|
|
# Wake at least every few seconds so a released lease frees the reaper.
|
|
clock.sleep(min(REAP_POLL, max(0.5, lease.expires - clock.now())))
|
|
return False
|