Добавил batch режим ручных Flutter команд
Этот коммит содержится в:
@@ -47,6 +47,14 @@ class ManualFlutterRequest(BaseModel):
|
|||||||
timeout: int | None = 3600
|
timeout: int | None = 3600
|
||||||
|
|
||||||
|
|
||||||
|
class ManualFlutterBatchRequest(BaseModel):
|
||||||
|
workspace: str
|
||||||
|
commands: str
|
||||||
|
device: str | None = None
|
||||||
|
timeout_per_command: int | None = 3600
|
||||||
|
stop_on_error: bool = True
|
||||||
|
|
||||||
|
|
||||||
def _runtime_env() -> dict:
|
def _runtime_env() -> dict:
|
||||||
runtime_home = os.getenv('DEVCONSOLE_RUNTIME_HOME') or os.getenv('HOME') or '/home/devconsole'
|
runtime_home = os.getenv('DEVCONSOLE_RUNTIME_HOME') or os.getenv('HOME') or '/home/devconsole'
|
||||||
flutter_home = os.getenv('FLUTTER_HOME') or f'{runtime_home}/flutter'
|
flutter_home = os.getenv('FLUTTER_HOME') or f'{runtime_home}/flutter'
|
||||||
@@ -86,12 +94,25 @@ def _split_args(raw_args: str) -> list[str]:
|
|||||||
return parts
|
return parts
|
||||||
|
|
||||||
|
|
||||||
|
def _split_batch(raw_commands: str) -> list[str]:
|
||||||
|
commands = []
|
||||||
|
for line in (raw_commands or '').splitlines():
|
||||||
|
cleaned = line.strip()
|
||||||
|
if not cleaned or cleaned.startswith('#'):
|
||||||
|
continue
|
||||||
|
commands.append(cleaned)
|
||||||
|
if not commands:
|
||||||
|
raise HTTPException(status_code=400, detail='Flutter command batch is empty')
|
||||||
|
if len(commands) > 30:
|
||||||
|
raise HTTPException(status_code=400, detail='Too many commands in batch')
|
||||||
|
return commands
|
||||||
|
|
||||||
|
|
||||||
def _event(payload: dict) -> str:
|
def _event(payload: dict) -> str:
|
||||||
return json.dumps(payload, ensure_ascii=False) + '\n'
|
return json.dumps(payload, ensure_ascii=False) + '\n'
|
||||||
|
|
||||||
|
|
||||||
def _stream_manual_flutter(workspace: str, args: str, device: str | None, timeout: int):
|
def _run_flutter_process(cwd: str, args: str, device: str | None, timeout: int, batch_index: int | None = None):
|
||||||
cwd = _workspace_cwd(workspace)
|
|
||||||
parts = _split_args(args)
|
parts = _split_args(args)
|
||||||
if device and '-d' not in parts and '--device-id' not in parts:
|
if device and '-d' not in parts and '--device-id' not in parts:
|
||||||
parts.extend(['-d', device])
|
parts.extend(['-d', device])
|
||||||
@@ -110,19 +131,20 @@ def _stream_manual_flutter(workspace: str, args: str, device: str | None, timeou
|
|||||||
preexec_fn=os.setsid,
|
preexec_fn=os.setsid,
|
||||||
)
|
)
|
||||||
ACTIVE_MANUAL_FLUTTER[task_id] = process
|
ACTIVE_MANUAL_FLUTTER[task_id] = process
|
||||||
add_log('Manual Flutter command started: ' + ' '.join(command))
|
label = ' '.join(command)
|
||||||
yield _event({'type': 'start', 'task_id': task_id, 'message': ' '.join(command)})
|
add_log('Manual Flutter command started: ' + label)
|
||||||
|
yield _event({'type': 'start', 'task_id': task_id, 'batch_index': batch_index, 'message': label})
|
||||||
|
|
||||||
started_at = time.time()
|
started_at = time.time()
|
||||||
try:
|
try:
|
||||||
assert process.stdout is not None
|
assert process.stdout is not None
|
||||||
while True:
|
while True:
|
||||||
if time.time() - started_at > timeout:
|
if time.time() - started_at > timeout:
|
||||||
yield _event({'type': 'error', 'task_id': task_id, 'message': 'Manual Flutter command timeout'})
|
yield _event({'type': 'error', 'task_id': task_id, 'batch_index': batch_index, 'message': 'Manual Flutter command timeout'})
|
||||||
break
|
break
|
||||||
line = process.stdout.readline()
|
line = process.stdout.readline()
|
||||||
if line:
|
if line:
|
||||||
yield _event({'type': 'line', 'task_id': task_id, 'message': line.rstrip()})
|
yield _event({'type': 'line', 'task_id': task_id, 'batch_index': batch_index, 'message': line.rstrip()})
|
||||||
continue
|
continue
|
||||||
if process.poll() is not None:
|
if process.poll() is not None:
|
||||||
break
|
break
|
||||||
@@ -134,8 +156,41 @@ def _stream_manual_flutter(workspace: str, args: str, device: str | None, timeou
|
|||||||
finally:
|
finally:
|
||||||
ACTIVE_MANUAL_FLUTTER.pop(task_id, None)
|
ACTIVE_MANUAL_FLUTTER.pop(task_id, None)
|
||||||
|
|
||||||
add_log('Manual Flutter command finished: ' + ' '.join(command) + f' exit {returncode}')
|
add_log('Manual Flutter command finished: ' + label + f' exit {returncode}')
|
||||||
yield _event({'type': 'done', 'task_id': task_id, 'returncode': returncode})
|
yield _event({'type': 'done', 'task_id': task_id, 'batch_index': batch_index, 'returncode': returncode, 'message': label})
|
||||||
|
return returncode
|
||||||
|
|
||||||
|
|
||||||
|
def _stream_manual_flutter(workspace: str, args: str, device: str | None, timeout: int):
|
||||||
|
cwd = _workspace_cwd(workspace)
|
||||||
|
yield from _run_flutter_process(cwd, args, device, timeout)
|
||||||
|
|
||||||
|
|
||||||
|
def _stream_manual_flutter_batch(payload: ManualFlutterBatchRequest):
|
||||||
|
cwd = _workspace_cwd(payload.workspace)
|
||||||
|
commands = _split_batch(payload.commands)
|
||||||
|
timeout = max(30, min(int(payload.timeout_per_command or 3600), 7200))
|
||||||
|
yield _event({'type': 'batch_start', 'message': f'Flutter batch started: {len(commands)} commands', 'total': len(commands)})
|
||||||
|
add_log(f'Manual Flutter batch started: {len(commands)} commands')
|
||||||
|
|
||||||
|
for index, command in enumerate(commands, start=1):
|
||||||
|
yield _event({'type': 'line', 'batch_index': index, 'message': f'▶ [{index}/{len(commands)}] {command}'})
|
||||||
|
returncode = None
|
||||||
|
runner = _run_flutter_process(cwd, command, payload.device, timeout, index)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
item = next(runner)
|
||||||
|
yield item
|
||||||
|
except StopIteration as stop:
|
||||||
|
returncode = stop.value
|
||||||
|
if returncode != 0 and payload.stop_on_error:
|
||||||
|
yield _event({'type': 'error', 'batch_index': index, 'message': f'Batch stopped on error: {command}'})
|
||||||
|
yield _event({'type': 'batch_done', 'success': False, 'failed_index': index})
|
||||||
|
add_log(f'Manual Flutter batch stopped on command {index}')
|
||||||
|
return
|
||||||
|
|
||||||
|
yield _event({'type': 'batch_done', 'success': True, 'message': 'Flutter batch completed'})
|
||||||
|
add_log('Manual Flutter batch completed')
|
||||||
|
|
||||||
|
|
||||||
@router.post('/flutter-manual-stream')
|
@router.post('/flutter-manual-stream')
|
||||||
@@ -145,3 +200,11 @@ async def flutter_manual_stream(payload: ManualFlutterRequest):
|
|||||||
_stream_manual_flutter(payload.workspace, payload.args, payload.device, timeout),
|
_stream_manual_flutter(payload.workspace, payload.args, payload.device, timeout),
|
||||||
media_type='application/x-ndjson',
|
media_type='application/x-ndjson',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/flutter-batch-stream')
|
||||||
|
async def flutter_batch_stream(payload: ManualFlutterBatchRequest):
|
||||||
|
return StreamingResponse(
|
||||||
|
_stream_manual_flutter_batch(payload),
|
||||||
|
media_type='application/x-ndjson',
|
||||||
|
)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user