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

Commit 1fb86f5e authored by Simon Chan's avatar Simon Chan
Browse files

feat(adb): buffer writes in sync push

parent 228d2ab7
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ export class Adb implements Closeable {
    ): Promise<Adb> {
        // Initially, set to highest-supported version and payload size.
        let version = 0x01000001;
        // Android 4: 4K, Android 7: 256K, Android 9: 1M
        let maxPayloadSize = 0x100000;

        const resolver = new PromiseResolver<string>();
@@ -167,11 +168,16 @@ export class Adb implements Closeable {
        return this.dispatcher.disconnected;
    }

    private _protocolVersion: number | undefined;
    private _protocolVersion: number;
    public get protocolVersion() {
        return this._protocolVersion;
    }

    private _maxPayloadSize: number;
    public get maxPayloadSize() {
        return this._maxPayloadSize;
    }

    private _product: string | undefined;
    public get product() {
        return this._product;
@@ -222,6 +228,7 @@ export class Adb implements Closeable {
        });

        this._protocolVersion = version;
        this._maxPayloadSize = maxPayloadSize;

        this.subprocess = new AdbSubprocess(this);
        this.power = new AdbPower(this);
+1 −0
Original line number Diff line number Diff line
@@ -3,5 +3,6 @@ export * from "./pull.js";
export * from "./push.js";
export * from "./request.js";
export * from "./response.js";
export * from "./socket.js";
export * from "./stat.js";
export * from "./sync.js";
+38 −16
Original line number Diff line number Diff line
import type {
    BufferedReadableStream,
    WritableStreamDefaultWriter,
} from "@yume-chan/stream-extra";
import Struct from "@yume-chan/struct";

import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js";
import type { AdbSyncSocket } from "./socket.js";
import type { AdbSyncStat } from "./stat.js";
import { AdbSyncLstatResponse, AdbSyncStatResponse } from "./stat.js";

@@ -31,16 +28,15 @@ export const AdbSyncEntry2Response = new Struct({ littleEndian: true })
export type AdbSyncEntry2Response =
    (typeof AdbSyncEntry2Response)["TDeserializeResult"];

export async function* adbSyncOpenDir(
    stream: BufferedReadableStream,
    writer: WritableStreamDefaultWriter<Uint8Array>,
    path: string,
    v2: boolean
): AsyncGenerator<AdbSyncEntry, void, void> {
    if (v2) {
        await adbSyncWriteRequest(writer, AdbSyncRequestId.List2, path);
export async function* adbSyncOpenDirV2(
    socket: AdbSyncSocket,
    path: string
): AsyncGenerator<AdbSyncEntry2Response, void, void> {
    const locked = await socket.lock();
    try {
        await adbSyncWriteRequest(locked, AdbSyncRequestId.List2, path);
        for await (const item of adbSyncReadResponses(
            stream,
            locked,
            AdbSyncResponseId.Entry2,
            AdbSyncEntry2Response
        )) {
@@ -52,13 +48,39 @@ export async function* adbSyncOpenDir(
            }
            yield item;
        }
    } else {
        await adbSyncWriteRequest(writer, AdbSyncRequestId.List, path);
    } finally {
        locked.release();
    }
}

export async function* adbSyncOpenDirV1(
    socket: AdbSyncSocket,
    path: string
): AsyncGenerator<AdbSyncEntryResponse, void, void> {
    const locked = await socket.lock();
    try {
        await adbSyncWriteRequest(locked, AdbSyncRequestId.List, path);
        for await (const item of adbSyncReadResponses(
            stream,
            locked,
            AdbSyncResponseId.Entry,
            AdbSyncEntryResponse
        )) {
            yield item;
        }
    } finally {
        locked.release();
    }
}

export async function* adbSyncOpenDir(
    socket: AdbSyncSocket,
    path: string,
    v2: boolean
): AsyncGenerator<AdbSyncEntry, void, void> {
    if (v2) {
        yield* adbSyncOpenDirV2(socket, path);
    } else {
        for await (const item of adbSyncOpenDirV1(socket, path)) {
            // Convert to same format as `AdbSyncEntry2Response` for easier consumption.
            // However it will add some overhead.
            yield {
+39 −44
Original line number Diff line number Diff line
import type {
    BufferedReadableStream,
    WritableStreamDefaultWriter,
} from "@yume-chan/stream-extra";
import { ReadableStream } from "@yume-chan/stream-extra";
import type { ReadableStream } from "@yume-chan/stream-extra";
import { PushReadableStream } from "@yume-chan/stream-extra";
import Struct from "@yume-chan/struct";

import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js";
import type { AdbSyncSocket } from "./socket.js";

export const AdbSyncDataResponse = new Struct({ littleEndian: true })
    .uint32("dataLength")
@@ -16,47 +14,44 @@ export const AdbSyncDataResponse = new Struct({ littleEndian: true })
export type AdbSyncDataResponse =
    (typeof AdbSyncDataResponse)["TDeserializeResult"];

export function adbSyncPull(
    stream: BufferedReadableStream,
    writer: WritableStreamDefaultWriter<Uint8Array>,
export async function* adbSyncPullGenerator(
    socket: AdbSyncSocket,
    path: string
): ReadableStream<Uint8Array> {
    let generator!: AsyncGenerator<AdbSyncDataResponse, void, void>;
    return new ReadableStream<Uint8Array>(
        {
            async start() {
                // TODO: If `ReadableStream.from(AsyncGenerator)` is added to spec, use it instead.
                await adbSyncWriteRequest(
                    writer,
                    AdbSyncRequestId.Receive,
                    path
                );
                generator = adbSyncReadResponses(
                    stream,
): AsyncGenerator<Uint8Array, void, void> {
    const locked = await socket.lock();
    let done = false;
    try {
        await adbSyncWriteRequest(locked, AdbSyncRequestId.Receive, path);
        for await (const packet of adbSyncReadResponses(
            locked,
            AdbSyncResponseId.Data,
            AdbSyncDataResponse
                );
            },
            async pull(controller) {
                const { done, value } = await generator.next();
                if (done) {
                    controller.close();
                    return;
        )) {
            yield packet.data;
        }
                controller.enqueue(value.data);
            },
            cancel() {
                generator.return().catch((e) => {
                    void e;
                });
                throw new Error(`Sync commands can't be canceled.`);
            },
        },
        {
            highWaterMark: 16 * 1024,
            size(chunk) {
                return chunk.byteLength;
            },
        done = true;
    } finally {
        if (!done) {
            // sync pull can't be cancelled, so we have to read all data
            for await (const packet of adbSyncReadResponses(
                locked,
                AdbSyncResponseId.Data,
                AdbSyncDataResponse
            )) {
                void packet;
            }
        }
        locked.release();
    }
    );
}

export function adbSyncPull(
    socket: AdbSyncSocket,
    path: string
): ReadableStream<Uint8Array> {
    return new PushReadableStream(async (controller) => {
        for await (const data of adbSyncPullGenerator(socket, path)) {
            await controller.enqueue(data);
        }
    });
}
+27 −18
Original line number Diff line number Diff line
import type {
    BufferedReadableStream,
    ReadableStream,
    WritableStreamDefaultWriter,
} from "@yume-chan/stream-extra";
import type { ReadableStream } from "@yume-chan/stream-extra";
import { ChunkStream, WritableStream } from "@yume-chan/stream-extra";
import Struct from "@yume-chan/struct";

import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js";
import type { AdbSyncSocket } from "./socket.js";
import { LinuxFileType } from "./stat.js";

export const AdbSyncOkResponse = new Struct({ littleEndian: true }).uint32(
@@ -17,25 +14,37 @@ export const AdbSyncOkResponse = new Struct({ littleEndian: true }).uint32(
export const ADB_SYNC_MAX_PACKET_SIZE = 64 * 1024;

export async function adbSyncPush(
    stream: BufferedReadableStream,
    writer: WritableStreamDefaultWriter<Uint8Array>,
    socket: AdbSyncSocket,
    filename: string,
    file: ReadableStream<Uint8Array>,
    mode: number = (LinuxFileType.File << 12) | 0o666,
    mtime: number = (Date.now() / 1000) | 0,
    packetSize: number = ADB_SYNC_MAX_PACKET_SIZE
) {
    const locked = await socket.lock();
    try {
        const pathAndMode = `${filename},${mode.toString()}`;
    await adbSyncWriteRequest(writer, AdbSyncRequestId.Send, pathAndMode);
        await adbSyncWriteRequest(locked, AdbSyncRequestId.Send, pathAndMode);

        await file.pipeThrough(new ChunkStream(packetSize)).pipeTo(
            new WritableStream({
                write: async (chunk) => {
                await adbSyncWriteRequest(writer, AdbSyncRequestId.Data, chunk);
                    await adbSyncWriteRequest(
                        locked,
                        AdbSyncRequestId.Data,
                        chunk
                    );
                },
            })
        );

    await adbSyncWriteRequest(writer, AdbSyncRequestId.Done, mtime);
    await adbSyncReadResponse(stream, AdbSyncResponseId.Ok, AdbSyncOkResponse);
        await adbSyncWriteRequest(locked, AdbSyncRequestId.Done, mtime);
        await adbSyncReadResponse(
            locked,
            AdbSyncResponseId.Ok,
            AdbSyncOkResponse
        );
    } finally {
        locked.release();
    }
}
Loading