mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 21:27:02 +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
116 lines
4.2 KiB
Python
116 lines
4.2 KiB
Python
"""CLI: ``python3 -m python_pkg.phone_lease acquire|release|status|holds|reap``.
|
|
|
|
The serial is optional everywhere; see :func:`serial.resolve_serial`.
|
|
Scope flags: ``--package <pkg>`` (one app) or ``--whole-phone``; neither means
|
|
the real screen, which is what every caller meant before scopes existed.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from dataclasses import asdict
|
|
import json
|
|
import sys
|
|
|
|
from python_pkg.phone_lease.lease import (
|
|
DEFAULT_TTL,
|
|
DEFAULT_WAIT,
|
|
PhoneBusyError,
|
|
Terms,
|
|
acquire,
|
|
scope_of,
|
|
)
|
|
from python_pkg.phone_lease.ops import holds, reap, release, status_all
|
|
from python_pkg.phone_lease.owner import owner_id
|
|
from python_pkg.phone_lease.serial import NoPhoneError, resolve_serial
|
|
|
|
|
|
def _scoped(parser: argparse.ArgumentParser) -> None:
|
|
parser.add_argument("serial", nargs="?", default=None)
|
|
which = parser.add_mutually_exclusive_group()
|
|
which.add_argument("--package", default=None, help="lease one app only")
|
|
which.add_argument(
|
|
"--whole-phone",
|
|
action="store_true",
|
|
help="phone-wide changes (Doze, sleep, reboot); capped at 300 s",
|
|
)
|
|
|
|
|
|
def _build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(prog="phone-lease", description=__doc__)
|
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
take = sub.add_parser(
|
|
"acquire", help="take or refresh a lease (exit 3 when held elsewhere)"
|
|
)
|
|
_scoped(take)
|
|
take.add_argument(
|
|
"--note", default="", help="what the holder is doing, shown to the other side"
|
|
)
|
|
take.add_argument("--ttl", type=float, default=DEFAULT_TTL)
|
|
take.add_argument("--wait", type=float, default=DEFAULT_WAIT)
|
|
take.add_argument(
|
|
"--cleanup",
|
|
action="append",
|
|
default=[],
|
|
help="command to run once on release or expiry (repeatable)",
|
|
)
|
|
_scoped(sub.add_parser("release", help="drop a lease this session holds"))
|
|
look = sub.add_parser("status", help="list live leases, or 'free'")
|
|
look.add_argument("serial", nargs="?", default=None)
|
|
look.add_argument("--json", action="store_true", help="machine-readable list")
|
|
ask = sub.add_parser("holds", help="exit 0 if OWNER may act on the scope")
|
|
_scoped(ask)
|
|
ask.add_argument("--owner", default=None, help="defaults to this session")
|
|
sub.add_parser("whoami", help="print this session's owner id")
|
|
reaper = sub.add_parser("reap", help="internal: run cleanup when a lease expires")
|
|
reaper.add_argument("serial")
|
|
reaper.add_argument("--owner", required=True)
|
|
reaper.add_argument("--scope", required=True)
|
|
return parser
|
|
|
|
|
|
def _run(args: argparse.Namespace) -> int:
|
|
if args.command == "whoami":
|
|
sys.stdout.write(f"{owner_id()}\n")
|
|
return 0
|
|
if args.command == "reap":
|
|
reap(args.serial, args.owner, args.scope)
|
|
return 0
|
|
serial = resolve_serial(args.serial)
|
|
if args.command == "status":
|
|
live = status_all(serial)
|
|
if args.json:
|
|
sys.stdout.write(json.dumps([asdict(x) for x in live]) + "\n")
|
|
else:
|
|
lines = [x.describe() for x in live] or ["free"]
|
|
sys.stdout.write("\n".join(lines) + "\n")
|
|
return 0
|
|
scope = scope_of(args.package, whole_phone=args.whole_phone)
|
|
if args.command == "holds":
|
|
return 0 if holds(serial, args.owner or owner_id(), scope) else 1
|
|
if args.command == "release":
|
|
dropped = release(serial, scope=scope)
|
|
sys.stdout.write("released\n" if dropped else f"not held by {owner_id()}\n")
|
|
return 0 if dropped else 1
|
|
terms = Terms(ttl=args.ttl, wait=args.wait, cleanup=tuple(args.cleanup))
|
|
lease = acquire(serial, args.note, scope=scope, terms=terms)
|
|
sys.stdout.write(f"{lease.describe()}\n")
|
|
return 0
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
"""Run the CLI. Exit 0 ok, 1 not held, 3 held elsewhere, 4 no phone."""
|
|
args = _build_parser().parse_args(argv)
|
|
try:
|
|
return _run(args)
|
|
except PhoneBusyError as exc:
|
|
sys.stderr.write(f"phone-lease: {exc}\n")
|
|
return 3
|
|
except NoPhoneError as exc:
|
|
sys.stderr.write(f"phone-lease: {exc}\n")
|
|
return 4
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|