mirror of
https://github.com/vesper8/Quest2Frame.git
synced 2026-10-10 06:00:58 +02:00
45 lines
2.7 KiB
Python
45 lines
2.7 KiB
Python
# SPDX-License-Identifier: GPL-3.0-only
|
|
"""Read-only storage discovery executed on Frame; never mounts/formats devices."""
|
|
PROBE = r'''
|
|
import json, os, pathlib, subprocess
|
|
home=os.path.expanduser('~')
|
|
def free(path):
|
|
s=os.statvfs(path);return s.f_bavail*s.f_frsize
|
|
result=[dict(id='internal',label='Internal storage',mount=home,free=free(home),uuid='')]
|
|
tree=json.loads(subprocess.check_output(['lsblk','-J','-o','NAME,PATH,TYPE,TRAN,RM,FSTYPE,UUID,MOUNTPOINTS'],text=True))
|
|
def walk(node,external=False,sd=False):
|
|
try:card_type=pathlib.Path('/sys/class/block',node['name'],'device/type').read_text().strip()
|
|
except OSError:card_type=''
|
|
sd=sd or card_type=='SD'
|
|
external=external or sd or node.get('rm') in (True,1,'1') or node.get('tran')=='usb'
|
|
if external and node.get('uuid') and node.get('fstype') in ('ext4','btrfs'):
|
|
for mount in node.get('mountpoints') or []:
|
|
if not mount or mount in ('/','/home','/var','/boot','/efi'):continue
|
|
try:
|
|
info=json.loads(subprocess.check_output(['findmnt','-J','-M',mount,'-o','OPTIONS'],text=True))['filesystems'][0]
|
|
flags=info['options'].split(',')
|
|
if 'rw' not in flags or 'noexec' in flags or not os.access(mount,os.W_OK|os.X_OK):continue
|
|
ident='uuid:'+node['uuid']
|
|
if any(x['id']==ident for x in result):continue
|
|
result.append(dict(id=ident,uuid=node['uuid'],mount=mount,free=free(mount),label=('SD card' if sd else 'Removable storage')))
|
|
except (OSError,subprocess.SubprocessError,KeyError,ValueError):continue
|
|
for child in node.get('children',[]):walk(child,external,sd)
|
|
for node in tree['blockdevices']:walk(node)
|
|
print(json.dumps(result))
|
|
'''
|
|
|
|
def select_storage(choices, ident):
|
|
matches=[item for item in choices if item['id']==ident]
|
|
if len(matches)!=1:raise RuntimeError('Selected storage is missing or not writable. Insert/mount the card and refresh storage; no internal fallback was used.')
|
|
return matches[0]
|
|
|
|
def card_launcher(uuid, package):
|
|
import shlex
|
|
q=shlex.quote
|
|
code="import json,subprocess; mounts=json.loads(subprocess.check_output(['findmnt','-J','-S',"+repr('UUID='+uuid)+",'-o','TARGET'],text=True))['filesystems']; assert len(mounts)==1, 'Storage mount is ambiguous'; print(mounts[0]['target'])"
|
|
return ('storage_mount=$(python3 -c '+q(code)+')\n'
|
|
'if [[ -z "$storage_mount" ]] || ! mountpoint -q -- "$storage_mount"; then\n'
|
|
' echo "Quest2Frame: required SD/removable storage is not mounted." >&2; exit 1\nfi\n'
|
|
'app_dir="$storage_mount"/'+q('Applications/quest-frame/'+package)+'\n'
|
|
'test -f "$app_dir/lepton-app/game.apk" || { echo "Game missing on selected storage" >&2; exit 1; }\n')
|