Добавил runtime операции для APK artifacts
Этот коммит содержится в:
@@ -10,7 +10,7 @@ from fastapi import APIRouter, HTTPException
|
|||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from backend.android_tools import find_apks
|
from backend.android_tools import find_apks, list_devices
|
||||||
from backend.ota_publish import publish_project_ota
|
from backend.ota_publish import publish_project_ota
|
||||||
from backend.runtime_logs import get_logs, add_log
|
from backend.runtime_logs import get_logs, add_log
|
||||||
from backend.shell_runner import run_command
|
from backend.shell_runner import run_command
|
||||||
@@ -35,9 +35,10 @@ def _flutter(command: str) -> str:
|
|||||||
|
|
||||||
class RuntimeCommandRequest(BaseModel):
|
class RuntimeCommandRequest(BaseModel):
|
||||||
workspace: str
|
workspace: str
|
||||||
command: str
|
command: str | None = None
|
||||||
device: str | None = None
|
device: str | None = None
|
||||||
package_name: str | None = None
|
package_name: str | None = None
|
||||||
|
apk_path: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class RuntimeStopRequest(BaseModel):
|
class RuntimeStopRequest(BaseModel):
|
||||||
@@ -64,6 +65,22 @@ def _workspace_cwd(workspace: str) -> str:
|
|||||||
return path.as_posix()
|
return path.as_posix()
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_apk_path(workspace: str, apk_path: str | None) -> Path:
|
||||||
|
if not apk_path:
|
||||||
|
raise HTTPException(status_code=400, detail='apk_path is required')
|
||||||
|
root = Path(workspace).resolve()
|
||||||
|
target = Path(apk_path).resolve()
|
||||||
|
if not root.exists():
|
||||||
|
raise HTTPException(status_code=400, detail='Workspace path does not exist')
|
||||||
|
if not target.exists() or not target.is_file():
|
||||||
|
raise HTTPException(status_code=404, detail='APK file not found')
|
||||||
|
if root not in target.parents and target != root:
|
||||||
|
raise HTTPException(status_code=403, detail='APK is outside workspace')
|
||||||
|
if target.suffix.lower() != '.apk':
|
||||||
|
raise HTTPException(status_code=403, detail='Only APK files are allowed')
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
def _with_device(command: str, device: str | None, enabled: bool) -> str:
|
def _with_device(command: str, device: str | None, enabled: bool) -> str:
|
||||||
if not enabled or not device:
|
if not enabled or not device:
|
||||||
return command
|
return command
|
||||||
@@ -221,6 +238,48 @@ async def runtime_install_latest_apk(payload: RuntimeCommandRequest):
|
|||||||
return {'success': result['returncode'] == 0, 'apk': apks[0], 'device': payload.device, 'result': result}
|
return {'success': result['returncode'] == 0, 'apk': apks[0], 'device': payload.device, 'result': result}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/install-apk')
|
||||||
|
async def runtime_install_apk(payload: RuntimeCommandRequest):
|
||||||
|
cwd = _workspace_cwd(payload.workspace)
|
||||||
|
apk = _safe_apk_path(cwd, payload.apk_path)
|
||||||
|
adb = f'{_runtime_env_prefix()}adb'
|
||||||
|
if payload.device:
|
||||||
|
adb = f'{_runtime_env_prefix()}adb -s {payload.device}'
|
||||||
|
command = f'{adb} install -r "{apk.as_posix()}"'
|
||||||
|
add_log(f'Runtime selected APK install started: {apk.name}')
|
||||||
|
result = run_command(command, cwd, timeout=900)
|
||||||
|
add_log(f"Runtime selected APK install finished: {apk.name} (exit {result['returncode']})")
|
||||||
|
return {'success': result['returncode'] == 0, 'apk': apk.as_posix(), 'device': payload.device, 'result': result}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/install-apk-all-devices')
|
||||||
|
async def runtime_install_apk_all_devices(payload: RuntimeCommandRequest):
|
||||||
|
cwd = _workspace_cwd(payload.workspace)
|
||||||
|
apk = _safe_apk_path(cwd, payload.apk_path)
|
||||||
|
devices = list_devices().get('devices') or []
|
||||||
|
installed = []
|
||||||
|
adb_base = f'{_runtime_env_prefix()}adb'
|
||||||
|
for device in devices:
|
||||||
|
serial = device.get('serial')
|
||||||
|
if not serial:
|
||||||
|
continue
|
||||||
|
command = f'{adb_base} -s {serial} install -r "{apk.as_posix()}"'
|
||||||
|
result = run_command(command, cwd, timeout=900)
|
||||||
|
installed.append({'serial': serial, 'success': result['returncode'] == 0, 'result': result})
|
||||||
|
add_log(f'Runtime APK install all devices finished: {apk.name}')
|
||||||
|
return {'success': all(item['success'] for item in installed) if installed else False, 'apk': apk.as_posix(), 'devices': installed}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/delete-apk')
|
||||||
|
async def runtime_delete_apk(payload: RuntimeCommandRequest):
|
||||||
|
cwd = _workspace_cwd(payload.workspace)
|
||||||
|
apk = _safe_apk_path(cwd, payload.apk_path)
|
||||||
|
apk_name = apk.name
|
||||||
|
apk.unlink()
|
||||||
|
add_log(f'APK artifact deleted: {apk_name}')
|
||||||
|
return {'success': True, 'deleted': apk_name}
|
||||||
|
|
||||||
|
|
||||||
@router.post('/restart-app')
|
@router.post('/restart-app')
|
||||||
async def runtime_restart_app(payload: RuntimeCommandRequest):
|
async def runtime_restart_app(payload: RuntimeCommandRequest):
|
||||||
if not payload.package_name:
|
if not payload.package_name:
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user