Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 17448eb1 authored by Simon Chan's avatar Simon Chan
Browse files

refactor(stream): create BufferedTransformStream to cover common...

refactor(stream): create BufferedTransformStream to cover common transformation usage of BufferedStream
parent 3a8ec412
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -44,20 +44,20 @@ export async function* adbSyncOpenDir(
    v2: boolean,
): AsyncGenerator<AdbSyncEntry, void, void> {
    let requestId: AdbSyncRequestId.List | AdbSyncRequestId.List2;
    let responseType: typeof LIST_V1_RESPONSE_TYPES | typeof LIST_V2_RESPONSE_TYPES;
    let responseTypes: typeof LIST_V1_RESPONSE_TYPES | typeof LIST_V2_RESPONSE_TYPES;

    if (v2) {
        requestId = AdbSyncRequestId.List2;
        responseType = LIST_V2_RESPONSE_TYPES;
        responseTypes = LIST_V2_RESPONSE_TYPES;
    } else {
        requestId = AdbSyncRequestId.List;
        responseType = LIST_V1_RESPONSE_TYPES;
        responseTypes = LIST_V1_RESPONSE_TYPES;
    }

    await adbSyncWriteRequest(writer, requestId, path);

    while (true) {
        const response = await adbSyncReadResponse(stream, responseType);
        const response = await adbSyncReadResponse(stream, responseTypes);
        switch (response.id) {
            case AdbSyncResponseId.Entry:
                yield {
+5 −4
Original line number Diff line number Diff line
@@ -115,23 +115,24 @@ export async function adbSyncLstat(
    v2: boolean,
): Promise<AdbSyncStat> {
    let requestId: AdbSyncRequestId.Lstat | AdbSyncRequestId.Lstat2;
    let responseType: typeof LSTAT_RESPONSE_TYPES | typeof LSTAT_V2_RESPONSE_TYPES;
    let responseTypes: typeof LSTAT_RESPONSE_TYPES | typeof LSTAT_V2_RESPONSE_TYPES;

    if (v2) {
        requestId = AdbSyncRequestId.Lstat2;
        responseType = LSTAT_V2_RESPONSE_TYPES;
        responseTypes = LSTAT_V2_RESPONSE_TYPES;
    } else {
        requestId = AdbSyncRequestId.Lstat;
        responseType = LSTAT_RESPONSE_TYPES;
        responseTypes = LSTAT_RESPONSE_TYPES;
    }

    await adbSyncWriteRequest(writer, requestId, path);
    const response = await adbSyncReadResponse(stream, responseType);
    const response = await adbSyncReadResponse(stream, responseTypes);

    switch (response.id) {
        case AdbSyncResponseId.Lstat:
            return {
                mode: response.mode,
                // Convert to `BigInt` to make it compatible with `AdbSyncStatResponse`
                size: BigInt(response.size),
                mtime: BigInt(response.mtime),
                get type() { return response.type; },
+4 −3
Original line number Diff line number Diff line
@@ -31,9 +31,10 @@ export class AdbSync extends AutoDisposable {
    protected adb: Adb;

    protected stream: BufferedReadableStream;

    // Getting another writer on a locked WritableStream will throw.
    // We don't want this behavior on higher-level APIs.
    // So we acquire the writer early and use a blocking lock to guard it.
    protected writer: WritableStreamDefaultWriter<Uint8Array>;

    protected sendLock = this.addDisposable(new AutoResetEvent());

    public get supportsStat(): boolean {
@@ -152,7 +153,7 @@ export class AdbSync extends AutoDisposable {
                if (this.needPushMkdirWorkaround) {
                    // It may fail if the path is already existed.
                    // Ignore the result.
                    // TODO: sync: test this
                    // TODO: sync: test push mkdir workaround (need an Android 8 device)
                    await this.adb.subprocess.spawnAndWait([
                        'mkdir',
                        '-p',
+17 −32
Original line number Diff line number Diff line
// cspell: ignore logcat

import { AdbCommandBase, AdbSubprocessNoneProtocol } from '@yume-chan/adb';
import { BufferedReadableStream, BufferedReadableStreamEndedError, DecodeUtf8Stream, ReadableStream, SplitStringStream, WritableStream } from '@yume-chan/stream-extra';
import { BufferedTransformStream, DecodeUtf8Stream, SplitStringStream, WrapReadableStream, WritableStream, type ReadableStream } from '@yume-chan/stream-extra';
import Struct, { decodeUtf8, StructAsyncDeserializeStream } from '@yume-chan/struct';

// `adb logcat` is an alias to `adb shell logcat`
@@ -185,9 +185,9 @@ export class Logcat extends AdbCommandBase {
    }

    public binary(options?: LogcatOptions): ReadableStream<AndroidLogEntry> {
        let bufferedStream: BufferedReadableStream;
        return new ReadableStream({
            start: async () => {
        return new WrapReadableStream(async () => {
            // TODO: make `spawn` return synchronously with streams pending
            // so it's easier to chain them.
            const { stdout } = await this.adb.subprocess.spawn([
                'logcat',
                '-B',
@@ -197,24 +197,9 @@ export class Logcat extends AdbCommandBase {
                // PERF: None protocol is 150% faster then Shell protocol
                protocols: [AdbSubprocessNoneProtocol],
            });
                bufferedStream = new BufferedReadableStream(stdout);
            },
            async pull(controller) {
                try {
                    const entry = await deserializeAndroidLogEntry(bufferedStream);
                    controller.enqueue(entry);
                } catch (e) {
                    if (e instanceof BufferedReadableStreamEndedError) {
                        controller.close();
                        return;
                    }

                    throw e;
                }
            },
            cancel() {
                bufferedStream.close();
            },
        });
            return stdout;
        }).pipeThrough(new BufferedTransformStream(stream => {
            return deserializeAndroidLogEntry(stream);
        }))
    }
}
+59 −0
Original line number Diff line number Diff line
import type { ValueOrPromise } from '@yume-chan/struct';
import { BufferedReadableStream, BufferedReadableStreamEndedError } from './buffered.js';
import { PushReadableStream, PushReadableStreamController } from './push-readable.js';
import { ReadableStream, ReadableWritablePair, WritableStream } from './stream.js';

// TODO: BufferedTransformStream: find better implementation
export class BufferedTransformStream<T> implements ReadableWritablePair<T, Uint8Array> {
    private _readable: ReadableStream<T>;
    public get readable() { return this._readable; }

    private _writable: WritableStream<Uint8Array>;
    public get writable() { return this._writable; }

    constructor(transform: (stream: BufferedReadableStream) => ValueOrPromise<T>) {
        // Convert incoming chunks to a `BufferedReadableStream`
        let sourceStreamController!: PushReadableStreamController<Uint8Array>;
        const sourceStream = new PushReadableStream<Uint8Array>(
            controller =>
                sourceStreamController = controller,
        )

        const buffered = new BufferedReadableStream(sourceStream);

        this._readable = new ReadableStream<T>({
            async pull(controller) {
                try {
                    const value = await transform(buffered);
                    controller.enqueue(value);
                } catch (e) {
                    // TODO: BufferedTransformStream: The semantic of stream ending is not clear
                    // If the `transform` started but did not finish, it should really be an error?
                    // But we can't detect that, unless there is a `peek` method on buffered stream.
                    if (e instanceof BufferedReadableStreamEndedError) {
                        controller.close();
                        return;
                    }
                    throw e;
                }
            },
            cancel: (reason) => {
                // Propagate cancel to the source stream
                // So future writes will be rejected
                sourceStream.cancel(reason);
            }
        });

        this._writable = new WritableStream({
            async write(chunk) {
                await sourceStreamController.enqueue(chunk);
            },
            abort() {
                sourceStreamController.close();
            },
            close() {
                sourceStreamController.close();
            },
        });
    }
}
Loading