mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-11 15:03:44 +08:00
99a8b1c07e
Manage named devices, car-first projection reconnect, and saved head-unit layouts. Keep runtime-safe Bluetooth operations available without changing controller ownership or restarting the radio unnecessarily.
106 lines
3.5 KiB
Python
106 lines
3.5 KiB
Python
"""The registered AGNOS radio preference in the global Params namespace.
|
|
|
|
The service uses /data/params/d independently of named application prefixes.
|
|
The key has no manager default: absence must not create or disable the radio."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import stat
|
|
import tempfile
|
|
|
|
|
|
RADIO_PREFERENCE = Path('/data/params/d/BluetoothEnabled')
|
|
|
|
|
|
class RadioPreference:
|
|
def __init__(self, path: Path = RADIO_PREFERENCE):
|
|
self.path = path
|
|
|
|
@staticmethod
|
|
def _read(path: Path) -> tuple[bytes | None, tuple | None]:
|
|
try:
|
|
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
|
|
except FileNotFoundError:
|
|
return None, None
|
|
with os.fdopen(fd, 'rb') as stream:
|
|
info = os.fstat(stream.fileno())
|
|
if not stat.S_ISREG(info.st_mode) or info.st_size > 16:
|
|
raise OSError('Invalid Bluetooth enable file')
|
|
value = stream.read(17)
|
|
if len(value) > 16:
|
|
raise OSError('Invalid Bluetooth enable file')
|
|
return value, (info.st_dev, info.st_ino, info.st_mtime_ns, info.st_size)
|
|
|
|
def enabled(self) -> bool:
|
|
value, _ = self._read(self.path)
|
|
return value is not None and value.rstrip(b'\n') == b'1'
|
|
|
|
def begin(self, enabled: bool) -> RadioPreferenceChange:
|
|
return RadioPreferenceChange(self.path, b'1' if enabled else b'0')
|
|
|
|
|
|
class RadioPreferenceChange:
|
|
def __init__(self, path: Path, value: bytes):
|
|
self.path = path
|
|
self.directory = path.parent.resolve(strict=True)
|
|
info = self.directory.stat()
|
|
self.directory_identity = (info.st_dev, info.st_ino)
|
|
self.target = self.directory / path.name
|
|
self.previous, self.previous_identity = RadioPreference._read(self.target)
|
|
self.value = value
|
|
self.owned_identity = None
|
|
|
|
def apply(self) -> None:
|
|
self._write(self.value, self.previous, self.previous_identity)
|
|
self.verify()
|
|
|
|
def current(self, value: bytes | None, identity: tuple | None) -> bool:
|
|
try:
|
|
directory = self.path.parent.resolve(strict=True)
|
|
info = directory.stat()
|
|
return (directory == self.directory and (info.st_dev, info.st_ino) == self.directory_identity and
|
|
RadioPreference._read(self.target) == (value, identity))
|
|
except OSError:
|
|
return False
|
|
|
|
def _write(self, value: bytes, expected: bytes | None, identity: tuple | None) -> None:
|
|
fd, temporary = tempfile.mkstemp(prefix='.bluetooth-enable-', dir=self.directory)
|
|
try:
|
|
with os.fdopen(fd, 'wb') as stream:
|
|
stream.write(value)
|
|
stream.flush()
|
|
os.fsync(stream.fileno())
|
|
info = os.fstat(stream.fileno())
|
|
written_identity = (info.st_dev, info.st_ino, info.st_mtime_ns, info.st_size)
|
|
if not self.current(expected, identity):
|
|
raise OSError('Bluetooth preference changed')
|
|
os.replace(temporary, self.target)
|
|
self.owned_identity = written_identity
|
|
self._sync()
|
|
finally:
|
|
if os.path.exists(temporary):
|
|
os.unlink(temporary)
|
|
|
|
def _sync(self) -> None:
|
|
fd = os.open(self.directory, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
|
try:
|
|
os.fsync(fd)
|
|
finally:
|
|
os.close(fd)
|
|
|
|
def verify(self) -> None:
|
|
if not self.current(self.value, self.owned_identity):
|
|
raise OSError('Bluetooth preference changed')
|
|
|
|
def rollback(self) -> bool:
|
|
if not self.current(self.value, self.owned_identity):
|
|
return False
|
|
if self.previous is None:
|
|
self.target.unlink()
|
|
self._sync()
|
|
else:
|
|
self._write(self.previous, self.value, self.owned_identity)
|
|
return True
|