mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 19:42:15 +02:00
refactor(adb): pre-encode packet ID as number in sync command
This commit is contained in:
parent
25efbe6402
commit
2db3e8f8b9
8 changed files with 90 additions and 48 deletions
|
@ -17,8 +17,7 @@ export interface AdbSyncEntry extends AdbSyncStat {
|
||||||
export const AdbSyncEntryResponse = new Struct({ littleEndian: true })
|
export const AdbSyncEntryResponse = new Struct({ littleEndian: true })
|
||||||
.concat(AdbSyncLstatResponse)
|
.concat(AdbSyncLstatResponse)
|
||||||
.uint32("nameLength")
|
.uint32("nameLength")
|
||||||
.string("name", { lengthField: "nameLength" })
|
.string("name", { lengthField: "nameLength" });
|
||||||
.extra({ id: AdbSyncResponseId.Entry as const });
|
|
||||||
|
|
||||||
export type AdbSyncEntryResponse =
|
export type AdbSyncEntryResponse =
|
||||||
(typeof AdbSyncEntryResponse)["TDeserializeResult"];
|
(typeof AdbSyncEntryResponse)["TDeserializeResult"];
|
||||||
|
@ -26,8 +25,7 @@ export type AdbSyncEntryResponse =
|
||||||
export const AdbSyncEntry2Response = new Struct({ littleEndian: true })
|
export const AdbSyncEntry2Response = new Struct({ littleEndian: true })
|
||||||
.concat(AdbSyncStatResponse)
|
.concat(AdbSyncStatResponse)
|
||||||
.uint32("nameLength")
|
.uint32("nameLength")
|
||||||
.string("name", { lengthField: "nameLength" })
|
.string("name", { lengthField: "nameLength" });
|
||||||
.extra({ id: AdbSyncResponseId.Entry2 as const });
|
|
||||||
|
|
||||||
export type AdbSyncEntry2Response =
|
export type AdbSyncEntry2Response =
|
||||||
(typeof AdbSyncEntry2Response)["TDeserializeResult"];
|
(typeof AdbSyncEntry2Response)["TDeserializeResult"];
|
||||||
|
|
|
@ -8,8 +8,7 @@ import type { AdbSyncSocket } from "./socket.js";
|
||||||
|
|
||||||
export const AdbSyncDataResponse = new Struct({ littleEndian: true })
|
export const AdbSyncDataResponse = new Struct({ littleEndian: true })
|
||||||
.uint32("dataLength")
|
.uint32("dataLength")
|
||||||
.uint8Array("data", { lengthField: "dataLength" })
|
.uint8Array("data", { lengthField: "dataLength" });
|
||||||
.extra({ id: AdbSyncResponseId.Data as const });
|
|
||||||
|
|
||||||
export type AdbSyncDataResponse =
|
export type AdbSyncDataResponse =
|
||||||
(typeof AdbSyncDataResponse)["TDeserializeResult"];
|
(typeof AdbSyncDataResponse)["TDeserializeResult"];
|
||||||
|
@ -52,6 +51,7 @@ export function adbSyncPull(
|
||||||
socket: AdbSyncSocket,
|
socket: AdbSyncSocket,
|
||||||
path: string,
|
path: string,
|
||||||
): ReadableStream<Uint8Array> {
|
): ReadableStream<Uint8Array> {
|
||||||
|
// TODO: use `ReadableStream.from` when it's supported
|
||||||
return new PushReadableStream(async (controller) => {
|
return new PushReadableStream(async (controller) => {
|
||||||
for await (const data of adbSyncPullGenerator(socket, path)) {
|
for await (const data of adbSyncPullGenerator(socket, path)) {
|
||||||
await controller.enqueue(data);
|
await controller.enqueue(data);
|
||||||
|
|
|
@ -111,7 +111,7 @@ export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdbSyncSendV2Request = new Struct({ littleEndian: true })
|
export const AdbSyncSendV2Request = new Struct({ littleEndian: true })
|
||||||
.uint32("id", placeholder<AdbSyncRequestId>())
|
.uint32("id")
|
||||||
.uint32("mode")
|
.uint32("mode")
|
||||||
.uint32("flags", placeholder<AdbSyncSendV2Flags>());
|
.uint32("flags", placeholder<AdbSyncSendV2Flags>());
|
||||||
|
|
||||||
|
|
|
@ -2,21 +2,23 @@ import Struct from "@yume-chan/struct";
|
||||||
|
|
||||||
import { encodeUtf8 } from "../../utils/index.js";
|
import { encodeUtf8 } from "../../utils/index.js";
|
||||||
|
|
||||||
export enum AdbSyncRequestId {
|
import { adbSyncEncodeId } from "./response.js";
|
||||||
List = "LIST",
|
|
||||||
ListV2 = "LIS2",
|
export namespace AdbSyncRequestId {
|
||||||
Send = "SEND",
|
export const List = adbSyncEncodeId("LIST");
|
||||||
SendV2 = "SND2",
|
export const ListV2 = adbSyncEncodeId("LIS2");
|
||||||
Lstat = "STAT",
|
export const Send = adbSyncEncodeId("SEND");
|
||||||
Stat = "STA2",
|
export const SendV2 = adbSyncEncodeId("SND2");
|
||||||
LstatV2 = "LST2",
|
export const Lstat = adbSyncEncodeId("STAT");
|
||||||
Data = "DATA",
|
export const Stat = adbSyncEncodeId("STA2");
|
||||||
Done = "DONE",
|
export const LstatV2 = adbSyncEncodeId("LST2");
|
||||||
Receive = "RECV",
|
export const Data = adbSyncEncodeId("DATA");
|
||||||
|
export const Done = adbSyncEncodeId("DONE");
|
||||||
|
export const Receive = adbSyncEncodeId("RECV");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdbSyncNumberRequest = new Struct({ littleEndian: true })
|
export const AdbSyncNumberRequest = new Struct({ littleEndian: true })
|
||||||
.string("id", { length: 4 })
|
.uint32("id")
|
||||||
.uint32("arg");
|
.uint32("arg");
|
||||||
|
|
||||||
export interface AdbSyncWritable {
|
export interface AdbSyncWritable {
|
||||||
|
@ -25,9 +27,13 @@ export interface AdbSyncWritable {
|
||||||
|
|
||||||
export async function adbSyncWriteRequest(
|
export async function adbSyncWriteRequest(
|
||||||
writable: AdbSyncWritable,
|
writable: AdbSyncWritable,
|
||||||
id: AdbSyncRequestId | string,
|
id: number | string,
|
||||||
value: number | string | Uint8Array,
|
value: number | string | Uint8Array,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
if (typeof id === "string") {
|
||||||
|
id = adbSyncEncodeId(id);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof value === "number") {
|
if (typeof value === "number") {
|
||||||
await writable.write(
|
await writable.write(
|
||||||
AdbSyncNumberRequest.serialize({ id, arg: value }),
|
AdbSyncNumberRequest.serialize({ id, arg: value }),
|
||||||
|
@ -39,9 +45,8 @@ export async function adbSyncWriteRequest(
|
||||||
value = encodeUtf8(value);
|
value = encodeUtf8(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `writable` will copy inputs to an internal buffer,
|
// `writable` is buffered, it copies inputs to an internal buffer,
|
||||||
// so we write header and `buffer` separately,
|
// so don't concatenate headers and data here, that will be an unnecessary copy.
|
||||||
// to avoid an extra copy.
|
|
||||||
await writable.write(
|
await writable.write(
|
||||||
AdbSyncNumberRequest.serialize({ id, arg: value.byteLength }),
|
AdbSyncNumberRequest.serialize({ id, arg: value.byteLength }),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getUint32LittleEndian } from "@yume-chan/no-data-view";
|
||||||
import type {
|
import type {
|
||||||
AsyncExactReadable,
|
AsyncExactReadable,
|
||||||
StructLike,
|
StructLike,
|
||||||
|
@ -7,16 +8,34 @@ import Struct from "@yume-chan/struct";
|
||||||
|
|
||||||
import { decodeUtf8 } from "../../utils/index.js";
|
import { decodeUtf8 } from "../../utils/index.js";
|
||||||
|
|
||||||
export enum AdbSyncResponseId {
|
function encodeAsciiUnchecked(value: string): Uint8Array {
|
||||||
Entry = "DENT",
|
const result = new Uint8Array(value.length);
|
||||||
Entry2 = "DNT2",
|
for (let i = 0; i < value.length; i += 1) {
|
||||||
Lstat = "STAT",
|
result[i] = value.charCodeAt(i);
|
||||||
Stat = "STA2",
|
}
|
||||||
Lstat2 = "LST2",
|
return result;
|
||||||
Done = "DONE",
|
}
|
||||||
Data = "DATA",
|
|
||||||
Ok = "OKAY",
|
/**
|
||||||
Fail = "FAIL",
|
* Encode ID to numbers for faster comparison
|
||||||
|
* @param value A 4-character string
|
||||||
|
* @returns A 32-bit integer by encoding the string as little-endian
|
||||||
|
*/
|
||||||
|
export function adbSyncEncodeId(value: string): number {
|
||||||
|
const buffer = encodeAsciiUnchecked(value);
|
||||||
|
return getUint32LittleEndian(buffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace AdbSyncResponseId {
|
||||||
|
export const Entry = adbSyncEncodeId("DENT");
|
||||||
|
export const Entry2 = adbSyncEncodeId("DNT2");
|
||||||
|
export const Lstat = adbSyncEncodeId("STAT");
|
||||||
|
export const Stat = adbSyncEncodeId("STA2");
|
||||||
|
export const Lstat2 = adbSyncEncodeId("LST2");
|
||||||
|
export const Done = adbSyncEncodeId("DONE");
|
||||||
|
export const Data = adbSyncEncodeId("DATA");
|
||||||
|
export const Ok = adbSyncEncodeId("OKAY");
|
||||||
|
export const Fail = adbSyncEncodeId("FAIL");
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AdbSyncError extends Error {}
|
export class AdbSyncError extends Error {}
|
||||||
|
@ -30,18 +49,24 @@ export const AdbSyncFailResponse = new Struct({ littleEndian: true })
|
||||||
|
|
||||||
export async function adbSyncReadResponse<T>(
|
export async function adbSyncReadResponse<T>(
|
||||||
stream: AsyncExactReadable,
|
stream: AsyncExactReadable,
|
||||||
id: AdbSyncResponseId,
|
id: number | string,
|
||||||
type: StructLike<T>,
|
type: StructLike<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const actualId = decodeUtf8(await stream.readExactly(4));
|
if (typeof id === "string") {
|
||||||
switch (actualId) {
|
id = adbSyncEncodeId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = await stream.readExactly(4);
|
||||||
|
switch (getUint32LittleEndian(buffer, 0)) {
|
||||||
case AdbSyncResponseId.Fail:
|
case AdbSyncResponseId.Fail:
|
||||||
await AdbSyncFailResponse.deserialize(stream);
|
await AdbSyncFailResponse.deserialize(stream);
|
||||||
throw new Error("Unreachable");
|
throw new Error("Unreachable");
|
||||||
case id:
|
case id:
|
||||||
return await type.deserialize(stream);
|
return await type.deserialize(stream);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Expected '${id}', but got '${actualId}'`);
|
throw new Error(
|
||||||
|
`Expected '${id}', but got '${decodeUtf8(buffer)}'`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,12 +74,16 @@ export async function* adbSyncReadResponses<
|
||||||
T extends Struct<object, PropertyKey, object, unknown>,
|
T extends Struct<object, PropertyKey, object, unknown>,
|
||||||
>(
|
>(
|
||||||
stream: AsyncExactReadable,
|
stream: AsyncExactReadable,
|
||||||
id: AdbSyncResponseId,
|
id: number | string,
|
||||||
type: T,
|
type: T,
|
||||||
): AsyncGenerator<StructValueType<T>, void, void> {
|
): AsyncGenerator<StructValueType<T>, void, void> {
|
||||||
|
if (typeof id === "string") {
|
||||||
|
id = adbSyncEncodeId(id);
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const actualId = decodeUtf8(await stream.readExactly(4));
|
const buffer = await stream.readExactly(4);
|
||||||
switch (actualId) {
|
switch (getUint32LittleEndian(buffer, 0)) {
|
||||||
case AdbSyncResponseId.Fail:
|
case AdbSyncResponseId.Fail:
|
||||||
await AdbSyncFailResponse.deserialize(stream);
|
await AdbSyncFailResponse.deserialize(stream);
|
||||||
throw new Error("Unreachable");
|
throw new Error("Unreachable");
|
||||||
|
@ -70,7 +99,7 @@ export async function* adbSyncReadResponses<
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected '${id}' or '${AdbSyncResponseId.Done}', but got '${actualId}'`,
|
`Expected '${id}' or '${AdbSyncResponseId.Done}', but got '${decodeUtf8(buffer)}'`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ export const AdbSyncLstatResponse = new Struct({ littleEndian: true })
|
||||||
.int32("size")
|
.int32("size")
|
||||||
.int32("mtime")
|
.int32("mtime")
|
||||||
.extra({
|
.extra({
|
||||||
id: AdbSyncResponseId.Lstat as const,
|
|
||||||
get type() {
|
get type() {
|
||||||
return (this.mode >> 12) as LinuxFileType;
|
return (this.mode >> 12) as LinuxFileType;
|
||||||
},
|
},
|
||||||
|
@ -83,7 +82,6 @@ export const AdbSyncStatResponse = new Struct({ littleEndian: true })
|
||||||
.uint64("mtime")
|
.uint64("mtime")
|
||||||
.uint64("ctime")
|
.uint64("ctime")
|
||||||
.extra({
|
.extra({
|
||||||
id: AdbSyncResponseId.Stat as const,
|
|
||||||
get type() {
|
get type() {
|
||||||
return (this.mode >> 12) as LinuxFileType;
|
return (this.mode >> 12) as LinuxFileType;
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,7 @@ import type { AdbSyncEntry } from "./list.js";
|
||||||
import { adbSyncOpenDir } from "./list.js";
|
import { adbSyncOpenDir } from "./list.js";
|
||||||
import { adbSyncPull } from "./pull.js";
|
import { adbSyncPull } from "./pull.js";
|
||||||
import { adbSyncPush } from "./push.js";
|
import { adbSyncPush } from "./push.js";
|
||||||
|
import type { AdbSyncSocketLocked } from "./socket.js";
|
||||||
import { AdbSyncSocket } from "./socket.js";
|
import { AdbSyncSocket } from "./socket.js";
|
||||||
import type { AdbSyncStat, LinuxFileType } from "./stat.js";
|
import type { AdbSyncStat, LinuxFileType } from "./stat.js";
|
||||||
import { adbSyncLstat, adbSyncStat } from "./stat.js";
|
import { adbSyncLstat, adbSyncStat } from "./stat.js";
|
||||||
|
@ -150,7 +151,7 @@ export class AdbSync extends AutoDisposable {
|
||||||
*/
|
*/
|
||||||
async write(options: AdbSyncWriteOptions): Promise<void> {
|
async write(options: AdbSyncWriteOptions): Promise<void> {
|
||||||
if (this.needPushMkdirWorkaround) {
|
if (this.needPushMkdirWorkaround) {
|
||||||
// It may fail if the path is already existed.
|
// It may fail if `filename` already exists.
|
||||||
// Ignore the result.
|
// Ignore the result.
|
||||||
// TODO: sync: test push mkdir workaround (need an Android 8 device)
|
// TODO: sync: test push mkdir workaround (need an Android 8 device)
|
||||||
await this._adb.subprocess.spawnAndWait([
|
await this._adb.subprocess.spawnAndWait([
|
||||||
|
@ -167,6 +168,10 @@ export class AdbSync extends AutoDisposable {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lockSocket(): Promise<AdbSyncSocketLocked> {
|
||||||
|
return this._socket.lock();
|
||||||
|
}
|
||||||
|
|
||||||
override async dispose() {
|
override async dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
await this._socket.close();
|
await this._socket.close();
|
||||||
|
|
|
@ -273,10 +273,17 @@ export class PackageManager extends AdbCommandBase {
|
||||||
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
|
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
|
||||||
);
|
);
|
||||||
if (!options?.skipExisting) {
|
if (!options?.skipExisting) {
|
||||||
// Today `pm` defaults to replace existing application (`-r` will be ignored),
|
/*
|
||||||
// but old versions defaults to skip existing application (like `-R`, but obviously
|
* | behavior | previous version | modern version |
|
||||||
// they didn't have this switch and ignores it if present).
|
* | -------------------- | -------------------- | -------------------- |
|
||||||
// If `skipExisting` is not set, add `-r` to ensure compatibility with old versions.
|
* | replace existing app | requires `-r` | default behavior [1] |
|
||||||
|
* | skip existing app | default behavior [2] | requires `-R` |
|
||||||
|
*
|
||||||
|
* [1]: `-r` recognized but ignored)
|
||||||
|
* [2]: `-R` not recognized but ignored
|
||||||
|
*
|
||||||
|
* So add `-r` when `skipExisting` is `false` for compatibility.
|
||||||
|
*/
|
||||||
args.push("-r");
|
args.push("-r");
|
||||||
}
|
}
|
||||||
return args;
|
return args;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue