feat: terminal, eventbus, load recipe
- change: disable reboot android when logged out - change: load recipe from android app's memory (requires ^0.0.3) - fix: adb payload handler may get incomplete message Signed-off-by: pakintada@gmail.com <Pakin>
This commit is contained in:
parent
ea7ec00b4b
commit
d4eb3be886
17 changed files with 2415 additions and 42 deletions
|
|
@ -11,6 +11,7 @@ import { Consumable, MaybeConsumable, ReadableStream } from '@yume-chan/stream-e
|
|||
import { AdbScrcpyClient } from '@yume-chan/adb-scrcpy';
|
||||
import { addNotification } from '../stores/noti';
|
||||
import { handleAdbPayload } from '../handlers/adbPayloadHandler';
|
||||
import { GlobalEventBus } from '../utils/eventBus';
|
||||
import { adbWriter } from '../stores/adbWriter';
|
||||
import { WritableStream } from '@yume-chan/stream-extra';
|
||||
import { env } from '$env/dynamic/public';
|
||||
|
|
@ -362,6 +363,83 @@ export async function executeCmd(command: string) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an ADB command and stream its output via callbacks.
|
||||
* Used for commands like `logcat` that run indefinitely.
|
||||
*
|
||||
* Returns a cleanup function that can be called to abort the stream.
|
||||
*/
|
||||
export async function executeStreamingCmd(
|
||||
command: string,
|
||||
callbacks: {
|
||||
onData?: (chunk: string) => void;
|
||||
onError?: (error: string) => void;
|
||||
onExit?: (exitCode: number | undefined) => void;
|
||||
}
|
||||
): Promise<() => void> {
|
||||
const instance = getAdbInstance();
|
||||
let aborted = false;
|
||||
|
||||
if (!instance) {
|
||||
callbacks.onError?.('No ADB device connected');
|
||||
return () => {};
|
||||
}
|
||||
|
||||
try {
|
||||
// NOTE: Always use noneProtocol.spawn() for streaming.
|
||||
// shellProtocol.spawnWaitText() waits for the process to exit — fine for
|
||||
// batch commands like 'echo foo', but logcat runs indefinitely, so the
|
||||
// Promise would never resolve and onData would never fire.
|
||||
const process = await instance.subprocess.noneProtocol.spawn(command);
|
||||
|
||||
const reader = process.output.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
// Start reading in the background
|
||||
(async () => {
|
||||
try {
|
||||
while (!aborted) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
const text = decoder.decode(value, { stream: true });
|
||||
if (text && !aborted) {
|
||||
callbacks.onData?.(text);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (!aborted) {
|
||||
callbacks.onError?.(e.message ?? 'Stream read error');
|
||||
}
|
||||
} finally {
|
||||
if (!aborted) {
|
||||
reader.releaseLock();
|
||||
callbacks.onExit?.(0);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Return cleanup function to abort the stream
|
||||
return () => {
|
||||
aborted = true;
|
||||
try {
|
||||
reader.cancel();
|
||||
} catch {
|
||||
// reader may already be done
|
||||
}
|
||||
};
|
||||
} catch (e: any) {
|
||||
if (!aborted) {
|
||||
if (e.message?.includes('ExactReadable ended')) {
|
||||
callbacks.onError?.('Connection closed');
|
||||
} else {
|
||||
callbacks.onError?.(e.message ?? 'Unknown error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return () => {};
|
||||
}
|
||||
|
||||
export async function disconnect() {
|
||||
let instance = getAdbInstance();
|
||||
if (instance) {
|
||||
|
|
@ -526,12 +604,32 @@ async function connectToAndroidServer(maxRetries = 5) {
|
|||
if (writer) {
|
||||
addNotification('INFO:Enable Brewing Mode T on machine');
|
||||
|
||||
const textDecoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
handleAdbPayload(new TextDecoder().decode(value));
|
||||
|
||||
// decode chunk
|
||||
buffer += textDecoder.decode(value, { stream: true });
|
||||
|
||||
let lines = buffer.split('\n');
|
||||
|
||||
// save potential incomplete
|
||||
buffer = lines.pop() ?? '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim() === '') continue;
|
||||
|
||||
GlobalEventBus.emit('adb:raw-payload', line);
|
||||
handleAdbPayload(line);
|
||||
}
|
||||
|
||||
// GlobalEventBus.emit('adb:raw-payload', new TextDecoder().decode(value));
|
||||
// handleAdbPayload(new TextDecoder().decode(value));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('read error', e);
|
||||
|
|
@ -637,6 +735,7 @@ async function connectToAndroidRecipeMenuServerOnce(notifyFailure = true, retryO
|
|||
const trimmedMessage = message.trim();
|
||||
if (trimmedMessage) {
|
||||
console.log('[ADB Reader] Processing message:', trimmedMessage.slice(0, 200));
|
||||
GlobalEventBus.emit('adb:raw-payload', trimmedMessage);
|
||||
handleAdbPayload(trimmedMessage);
|
||||
}
|
||||
}
|
||||
|
|
@ -644,6 +743,7 @@ async function connectToAndroidRecipeMenuServerOnce(notifyFailure = true, retryO
|
|||
|
||||
const remainingMessage = messageBuffer.trim();
|
||||
if (remainingMessage) {
|
||||
GlobalEventBus.emit('adb:raw-payload', remainingMessage);
|
||||
handleAdbPayload(remainingMessage);
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue