@yume-chan/adb 0.0.11 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +39 -0
- package/CHANGELOG.md +23 -1
- package/README.md +24 -17
- package/esm/adb.d.ts +10 -9
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +87 -64
- package/esm/adb.js.map +1 -1
- package/esm/auth.d.ts +6 -6
- package/esm/auth.d.ts.map +1 -1
- package/esm/auth.js +8 -6
- package/esm/auth.js.map +1 -1
- package/esm/backend.d.ts +2 -2
- package/esm/commands/install.d.ts.map +1 -1
- package/esm/commands/install.js +5 -7
- package/esm/commands/install.js.map +1 -1
- package/esm/commands/reverse.d.ts +7 -10
- package/esm/commands/reverse.d.ts.map +1 -1
- package/esm/commands/reverse.js +9 -15
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/index.d.ts +19 -20
- package/esm/commands/subprocess/index.d.ts.map +1 -1
- package/esm/commands/subprocess/index.js +36 -31
- package/esm/commands/subprocess/index.js.map +1 -1
- package/esm/commands/subprocess/legacy.d.ts +14 -7
- package/esm/commands/subprocess/legacy.d.ts.map +1 -1
- package/esm/commands/subprocess/legacy.js +14 -5
- package/esm/commands/subprocess/legacy.js.map +1 -1
- package/esm/commands/subprocess/protocol.d.ts +3 -2
- package/esm/commands/subprocess/protocol.d.ts.map +1 -1
- package/esm/commands/subprocess/protocol.js +6 -4
- package/esm/commands/subprocess/protocol.js.map +1 -1
- package/esm/commands/subprocess/protocols/index.d.ts +4 -0
- package/esm/commands/subprocess/protocols/index.d.ts.map +1 -0
- package/esm/commands/subprocess/protocols/index.js +4 -0
- package/esm/commands/subprocess/protocols/index.js.map +1 -0
- package/esm/commands/subprocess/protocols/none.d.ts +35 -0
- package/esm/commands/subprocess/protocols/none.d.ts.map +1 -0
- package/esm/commands/subprocess/protocols/none.js +53 -0
- package/esm/commands/subprocess/protocols/none.js.map +1 -0
- package/esm/commands/subprocess/protocols/shell.d.ts +39 -0
- package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -0
- package/esm/commands/subprocess/protocols/shell.js +155 -0
- package/esm/commands/subprocess/protocols/shell.js.map +1 -0
- package/esm/commands/subprocess/protocols/types.d.ts +50 -0
- package/esm/commands/subprocess/protocols/types.d.ts.map +1 -0
- package/esm/commands/subprocess/protocols/types.js +2 -0
- package/esm/commands/subprocess/protocols/types.js.map +1 -0
- package/esm/commands/subprocess/types.d.ts +9 -6
- package/esm/commands/subprocess/types.d.ts.map +1 -1
- package/esm/commands/sync/list.d.ts +25 -1
- package/esm/commands/sync/list.d.ts.map +1 -1
- package/esm/commands/sync/list.js +40 -5
- package/esm/commands/sync/list.js.map +1 -1
- package/esm/commands/sync/pull.d.ts.map +1 -1
- package/esm/commands/sync/pull.js +6 -3
- package/esm/commands/sync/pull.js.map +1 -1
- package/esm/commands/sync/request.d.ts +1 -0
- package/esm/commands/sync/request.d.ts.map +1 -1
- package/esm/commands/sync/request.js +1 -0
- package/esm/commands/sync/request.js.map +1 -1
- package/esm/commands/sync/response.d.ts +2 -1
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +2 -1
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/stat.d.ts +13 -1
- package/esm/commands/sync/stat.d.ts.map +1 -1
- package/esm/commands/sync/stat.js +20 -7
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.d.ts +13 -35
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +39 -9
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/crypto.d.ts +23 -10
- package/esm/crypto.d.ts.map +1 -1
- package/esm/crypto.js +64 -46
- package/esm/crypto.js.map +1 -1
- package/esm/features.d.ts +4 -2
- package/esm/features.d.ts.map +1 -1
- package/esm/features.js +6 -2
- package/esm/features.js.map +1 -1
- package/esm/packet.d.ts +21 -2
- package/esm/packet.d.ts.map +1 -1
- package/esm/packet.js +2 -2
- package/esm/packet.js.map +1 -1
- package/esm/socket/dispatcher.d.ts +34 -23
- package/esm/socket/dispatcher.d.ts.map +1 -1
- package/esm/socket/dispatcher.js +92 -70
- package/esm/socket/dispatcher.js.map +1 -1
- package/esm/socket/socket.d.ts +29 -8
- package/esm/socket/socket.d.ts.map +1 -1
- package/esm/socket/socket.js +41 -27
- package/esm/socket/socket.js.map +1 -1
- package/esm/stream/detect.d.ts +1 -17
- package/esm/stream/detect.d.ts.map +1 -1
- package/esm/stream/detect.js +4 -5
- package/esm/stream/detect.js.map +1 -1
- package/esm/stream/detect.polyfill.d.ts.map +1 -1
- package/esm/stream/detect.polyfill.js +4 -1
- package/esm/stream/detect.polyfill.js.map +1 -1
- package/esm/stream/transform.d.ts +37 -32
- package/esm/stream/transform.d.ts.map +1 -1
- package/esm/stream/transform.js +106 -92
- package/esm/stream/transform.js.map +1 -1
- package/esm/utils/encoding.d.ts.map +1 -1
- package/esm/utils/encoding.js.map +1 -1
- package/esm/utils/index.d.ts +1 -1
- package/esm/utils/index.d.ts.map +1 -1
- package/esm/utils/index.js +1 -1
- package/esm/utils/index.js.map +1 -1
- package/package.json +6 -6
- package/src/adb.ts +105 -76
- package/src/auth.ts +21 -18
- package/src/backend.ts +2 -2
- package/src/commands/install.ts +7 -9
- package/src/commands/reverse.ts +13 -24
- package/src/commands/subprocess/index.ts +57 -42
- package/src/commands/subprocess/protocols/index.ts +3 -0
- package/src/commands/subprocess/{legacy.ts → protocols/none.ts} +20 -10
- package/src/commands/subprocess/{protocol.ts → protocols/shell.ts} +22 -17
- package/src/commands/subprocess/{types.ts → protocols/types.ts} +13 -9
- package/src/commands/sync/list.ts +51 -5
- package/src/commands/sync/pull.ts +6 -3
- package/src/commands/sync/request.ts +1 -0
- package/src/commands/sync/response.ts +8 -2
- package/src/commands/sync/stat.ts +36 -9
- package/src/commands/sync/sync.ts +55 -19
- package/src/crypto.ts +72 -56
- package/src/features.ts +6 -2
- package/src/packet.ts +17 -6
- package/src/socket/dispatcher.ts +133 -88
- package/src/socket/socket.ts +61 -48
- package/src/stream/detect.polyfill.ts +4 -1
- package/src/stream/detect.ts +4 -24
- package/src/stream/transform.ts +133 -116
- package/src/utils/index.ts +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/src/utils/encoding.ts +0 -13
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Adb } from "
|
|
2
|
-
import type { AdbSocket } from "
|
|
3
|
-
import { DuplexStreamFactory,
|
|
1
|
+
import type { Adb } from "../../../adb.js";
|
|
2
|
+
import type { AdbSocket } from "../../../socket/index.js";
|
|
3
|
+
import { DuplexStreamFactory, ReadableStream } from "../../../stream/index.js";
|
|
4
4
|
import type { AdbSubprocessProtocol } from "./types.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -11,11 +11,17 @@ import type { AdbSubprocessProtocol } from "./types.js";
|
|
|
11
11
|
* * `exit` exit code: No
|
|
12
12
|
* * `resize`: No
|
|
13
13
|
*/
|
|
14
|
-
export class
|
|
14
|
+
export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|
15
15
|
public static isSupported() { return true; }
|
|
16
16
|
|
|
17
|
-
public static async
|
|
18
|
-
return new
|
|
17
|
+
public static async pty(adb: Adb, command: string) {
|
|
18
|
+
return new AdbSubprocessNoneProtocol(await adb.createSocket(`shell:${command}`));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static async raw(adb: Adb, command: string) {
|
|
22
|
+
// `shell,raw:${command}` also triggers raw mode,
|
|
23
|
+
// But is not supported before Android 7.
|
|
24
|
+
return new AdbSubprocessNoneProtocol(await adb.createSocket(`exec:${command}`));
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
private readonly socket: AdbSocket;
|
|
@@ -24,11 +30,15 @@ export class AdbNoneSubprocessProtocol implements AdbSubprocessProtocol {
|
|
|
24
30
|
public get stdin() { return this.socket.writable; }
|
|
25
31
|
|
|
26
32
|
private _stdout: ReadableStream<Uint8Array>;
|
|
27
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Legacy shell mixes stdout and stderr.
|
|
35
|
+
*/
|
|
28
36
|
public get stdout() { return this._stdout; }
|
|
29
37
|
|
|
30
|
-
// `stderr` of Legacy shell is always empty.
|
|
31
38
|
private _stderr: ReadableStream<Uint8Array>;
|
|
39
|
+
/**
|
|
40
|
+
* `stderr` will always be empty.
|
|
41
|
+
*/
|
|
32
42
|
public get stderr() { return this._stderr; }
|
|
33
43
|
|
|
34
44
|
private _exit: Promise<number>;
|
|
@@ -43,8 +53,8 @@ export class AdbNoneSubprocessProtocol implements AdbSubprocessProtocol {
|
|
|
43
53
|
},
|
|
44
54
|
});
|
|
45
55
|
|
|
46
|
-
this._stdout = factory.
|
|
47
|
-
this._stderr = factory.
|
|
56
|
+
this._stdout = factory.wrapReadable(this.socket.readable);
|
|
57
|
+
this._stderr = factory.wrapReadable(new ReadableStream());
|
|
48
58
|
this._exit = factory.closed.then(() => 0);
|
|
49
59
|
}
|
|
50
60
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { PromiseResolver } from "@yume-chan/async";
|
|
2
2
|
import Struct, { placeholder, type StructValueType } from "@yume-chan/struct";
|
|
3
|
-
import type { Adb } from "
|
|
4
|
-
import { AdbFeatures } from "
|
|
5
|
-
import type { AdbSocket } from "
|
|
6
|
-
import { PushReadableStream, ReadableStream, StructDeserializeStream, StructSerializeStream, TransformStream, WritableStream, WritableStreamDefaultWriter, type PushReadableStreamController } from "
|
|
7
|
-
import { encodeUtf8 } from "
|
|
3
|
+
import type { Adb } from "../../../adb.js";
|
|
4
|
+
import { AdbFeatures } from "../../../features.js";
|
|
5
|
+
import type { AdbSocket } from "../../../socket/index.js";
|
|
6
|
+
import { pipeFrom, PushReadableStream, ReadableStream, StructDeserializeStream, StructSerializeStream, TransformStream, WritableStream, WritableStreamDefaultWriter, type PushReadableStreamController } from "../../../stream/index.js";
|
|
7
|
+
import { encodeUtf8 } from "../../../utils/index.js";
|
|
8
8
|
import type { AdbSubprocessProtocol } from "./types.js";
|
|
9
9
|
|
|
10
10
|
export enum AdbShellProtocolId {
|
|
@@ -17,10 +17,11 @@ export enum AdbShellProtocolId {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// This packet format is used in both direction.
|
|
20
|
-
const AdbShellProtocolPacket =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const AdbShellProtocolPacket =
|
|
21
|
+
new Struct({ littleEndian: true })
|
|
22
|
+
.uint8('id', placeholder<AdbShellProtocolId>())
|
|
23
|
+
.uint32('length')
|
|
24
|
+
.uint8Array('data', { lengthField: 'length' });
|
|
24
25
|
|
|
25
26
|
type AdbShellProtocolPacketInit = typeof AdbShellProtocolPacket['TInit'];
|
|
26
27
|
|
|
@@ -99,15 +100,18 @@ class MultiplexStream<T>{
|
|
|
99
100
|
* * `exit` exit code: Yes
|
|
100
101
|
* * `resize`: Yes
|
|
101
102
|
*/
|
|
102
|
-
export class
|
|
103
|
+
export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|
103
104
|
public static isSupported(adb: Adb) {
|
|
104
105
|
return adb.features!.includes(AdbFeatures.ShellV2);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
public static async
|
|
108
|
-
// TODO: AdbShellSubprocessProtocol: Support raw mode
|
|
108
|
+
public static async pty(adb: Adb, command: string) {
|
|
109
109
|
// TODO: AdbShellSubprocessProtocol: Support setting `XTERM` environment variable
|
|
110
|
-
return new
|
|
110
|
+
return new AdbSubprocessShellProtocol(await adb.createSocket(`shell,v2,pty:${command}`));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public static async raw(adb: Adb, command: string) {
|
|
114
|
+
return new AdbSubprocessShellProtocol(await adb.createSocket(`shell,v2,raw:${command}`));
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
private readonly _socket: AdbSocket;
|
|
@@ -140,7 +144,7 @@ export class AdbShellSubprocessProtocol implements AdbSubprocessProtocol {
|
|
|
140
144
|
transform: (chunk, controller) => {
|
|
141
145
|
if (chunk.id === AdbShellProtocolId.Exit) {
|
|
142
146
|
this._exit.resolve(new Uint8Array(chunk.data)[0]!);
|
|
143
|
-
// We can let `
|
|
147
|
+
// We can let `StdoutDeserializeStream` to process `AdbShellProtocolId.Exit`,
|
|
144
148
|
// but since we need this `TransformStream` to capture the exit code anyway,
|
|
145
149
|
// terminating child streams here is killing two birds with one stone.
|
|
146
150
|
controller.terminate();
|
|
@@ -160,9 +164,10 @@ export class AdbShellSubprocessProtocol implements AdbSubprocessProtocol {
|
|
|
160
164
|
.pipeThrough(new StructSerializeStream(AdbShellProtocolPacket))
|
|
161
165
|
.pipeTo(socket.writable);
|
|
162
166
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
this._stdin = pipeFrom(
|
|
168
|
+
multiplexer.createWriteable(),
|
|
169
|
+
new StdinSerializeStream()
|
|
170
|
+
);
|
|
166
171
|
|
|
167
172
|
this._socketWriter = multiplexer.createWriteable().getWriter();
|
|
168
173
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ValueOrPromise } from "@yume-chan/struct";
|
|
2
|
-
import type { Adb } from "
|
|
3
|
-
import type { AdbSocket } from "
|
|
4
|
-
import type { ReadableStream, WritableStream } from "
|
|
2
|
+
import type { Adb } from "../../../adb.js";
|
|
3
|
+
import type { AdbSocket } from "../../../socket/index.js";
|
|
4
|
+
import type { ReadableStream, WritableStream } from "../../../stream/index.js";
|
|
5
5
|
|
|
6
6
|
export interface AdbSubprocessProtocol {
|
|
7
7
|
/**
|
|
@@ -17,7 +17,7 @@ export interface AdbSubprocessProtocol {
|
|
|
17
17
|
/**
|
|
18
18
|
* The `stderr` pipe of the process.
|
|
19
19
|
*
|
|
20
|
-
* Note: Some `
|
|
20
|
+
* Note: Some `AdbSubprocessProtocol` doesn't separate `stdout` and `stderr`,
|
|
21
21
|
* All output will be sent to `stdout`.
|
|
22
22
|
*/
|
|
23
23
|
readonly stderr: ReadableStream<Uint8Array>;
|
|
@@ -25,15 +25,16 @@ export interface AdbSubprocessProtocol {
|
|
|
25
25
|
/**
|
|
26
26
|
* A `Promise` that resolves to the exit code of the process.
|
|
27
27
|
*
|
|
28
|
-
* Note: Some `
|
|
29
|
-
* They will always resolve with `0`.
|
|
28
|
+
* Note: Some `AdbSubprocessProtocol` doesn't support exit code,
|
|
29
|
+
* They will always resolve it with `0`.
|
|
30
30
|
*/
|
|
31
31
|
readonly exit: Promise<number>;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Resizes the current shell.
|
|
35
35
|
*
|
|
36
|
-
* Some `
|
|
36
|
+
* Some `AdbSubprocessProtocol`s may not support resizing
|
|
37
|
+
* and will ignore calls to this method.
|
|
37
38
|
*/
|
|
38
39
|
resize(rows: number, cols: number): ValueOrPromise<void>;
|
|
39
40
|
|
|
@@ -47,8 +48,11 @@ export interface AdbSubprocessProtocolConstructor {
|
|
|
47
48
|
/** Returns `true` if the `adb` instance supports this shell */
|
|
48
49
|
isSupported(adb: Adb): ValueOrPromise<boolean>;
|
|
49
50
|
|
|
50
|
-
/**
|
|
51
|
-
|
|
51
|
+
/** Spawns an executable in PTY (interactive) mode. */
|
|
52
|
+
pty(adb: Adb, command: string): ValueOrPromise<AdbSubprocessProtocol>;
|
|
53
|
+
|
|
54
|
+
/** Spawns an executable and pipe the output. */
|
|
55
|
+
raw(adb: Adb, command: string): ValueOrPromise<AdbSubprocessProtocol>;
|
|
52
56
|
|
|
53
57
|
/** Creates a new `AdbShell` by attaching to an exist `AdbSocket` */
|
|
54
58
|
new(socket: AdbSocket): AdbSubprocessProtocol;
|
|
@@ -2,7 +2,11 @@ import Struct from '@yume-chan/struct';
|
|
|
2
2
|
import type { AdbBufferedStream, WritableStreamDefaultWriter } from '../../stream/index.js';
|
|
3
3
|
import { AdbSyncRequestId, adbSyncWriteRequest } from './request.js';
|
|
4
4
|
import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './response.js';
|
|
5
|
-
import { AdbSyncLstatResponse } from './stat.js';
|
|
5
|
+
import { AdbSyncLstatResponse, AdbSyncStatResponse, type AdbSyncStat } from './stat.js';
|
|
6
|
+
|
|
7
|
+
export interface AdbSyncEntry extends AdbSyncStat {
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
6
10
|
|
|
7
11
|
export const AdbSyncEntryResponse =
|
|
8
12
|
new Struct({ littleEndian: true })
|
|
@@ -13,22 +17,64 @@ export const AdbSyncEntryResponse =
|
|
|
13
17
|
|
|
14
18
|
export type AdbSyncEntryResponse = typeof AdbSyncEntryResponse['TDeserializeResult'];
|
|
15
19
|
|
|
16
|
-
const
|
|
20
|
+
export const AdbSyncEntry2Response =
|
|
21
|
+
new Struct({ littleEndian: true })
|
|
22
|
+
.fields(AdbSyncStatResponse)
|
|
23
|
+
.uint32('nameLength')
|
|
24
|
+
.string('name', { lengthField: 'nameLength' })
|
|
25
|
+
.extra({ id: AdbSyncResponseId.Entry2 as const });
|
|
26
|
+
|
|
27
|
+
export type AdbSyncEntry2Response = typeof AdbSyncEntry2Response['TDeserializeResult'];
|
|
28
|
+
|
|
29
|
+
const LIST_V1_RESPONSE_TYPES = {
|
|
17
30
|
[AdbSyncResponseId.Entry]: AdbSyncEntryResponse,
|
|
18
31
|
[AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncEntryResponse.size),
|
|
19
32
|
};
|
|
20
33
|
|
|
34
|
+
const LIST_V2_RESPONSE_TYPES = {
|
|
35
|
+
[AdbSyncResponseId.Entry2]: AdbSyncEntry2Response,
|
|
36
|
+
[AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncEntry2Response.size),
|
|
37
|
+
};
|
|
38
|
+
|
|
21
39
|
export async function* adbSyncOpenDir(
|
|
22
40
|
stream: AdbBufferedStream,
|
|
23
41
|
writer: WritableStreamDefaultWriter<Uint8Array>,
|
|
24
42
|
path: string,
|
|
25
|
-
|
|
26
|
-
|
|
43
|
+
v2: boolean,
|
|
44
|
+
): AsyncGenerator<AdbSyncEntry, void, void> {
|
|
45
|
+
let requestId: AdbSyncRequestId.List | AdbSyncRequestId.List2;
|
|
46
|
+
let responseType: typeof LIST_V1_RESPONSE_TYPES | typeof LIST_V2_RESPONSE_TYPES;
|
|
47
|
+
|
|
48
|
+
if (v2) {
|
|
49
|
+
requestId = AdbSyncRequestId.List2;
|
|
50
|
+
responseType = LIST_V2_RESPONSE_TYPES;
|
|
51
|
+
} else {
|
|
52
|
+
requestId = AdbSyncRequestId.List;
|
|
53
|
+
responseType = LIST_V1_RESPONSE_TYPES;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await adbSyncWriteRequest(writer, requestId, path);
|
|
27
57
|
|
|
28
58
|
while (true) {
|
|
29
|
-
const response = await adbSyncReadResponse(stream,
|
|
59
|
+
const response = await adbSyncReadResponse(stream, responseType);
|
|
30
60
|
switch (response.id) {
|
|
31
61
|
case AdbSyncResponseId.Entry:
|
|
62
|
+
yield {
|
|
63
|
+
mode: response.mode,
|
|
64
|
+
size: BigInt(response.size),
|
|
65
|
+
mtime: BigInt(response.mtime),
|
|
66
|
+
get type() { return response.type; },
|
|
67
|
+
get permission() { return response.permission; },
|
|
68
|
+
name: response.name,
|
|
69
|
+
};
|
|
70
|
+
break;
|
|
71
|
+
case AdbSyncResponseId.Entry2:
|
|
72
|
+
// `LST2` can return error codes for failed `lstat` calls.
|
|
73
|
+
// `LIST` just ignores them.
|
|
74
|
+
// But they only contain `name` so still pretty useless.
|
|
75
|
+
if (response.error !== 0) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
32
78
|
yield response;
|
|
33
79
|
break;
|
|
34
80
|
case AdbSyncResponseId.Done:
|
|
@@ -9,7 +9,7 @@ export const AdbSyncDataResponse =
|
|
|
9
9
|
.uint8Array('data', { lengthField: 'dataLength' })
|
|
10
10
|
.extra({ id: AdbSyncResponseId.Data as const });
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const RESPONSE_TYPES = {
|
|
13
13
|
[AdbSyncResponseId.Data]: AdbSyncDataResponse,
|
|
14
14
|
[AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncDataResponse.size),
|
|
15
15
|
};
|
|
@@ -24,7 +24,7 @@ export function adbSyncPull(
|
|
|
24
24
|
await adbSyncWriteRequest(writer, AdbSyncRequestId.Receive, path);
|
|
25
25
|
},
|
|
26
26
|
async pull(controller) {
|
|
27
|
-
const response = await adbSyncReadResponse(stream,
|
|
27
|
+
const response = await adbSyncReadResponse(stream, RESPONSE_TYPES);
|
|
28
28
|
switch (response.id) {
|
|
29
29
|
case AdbSyncResponseId.Data:
|
|
30
30
|
controller.enqueue(response.data!);
|
|
@@ -35,7 +35,10 @@ export function adbSyncPull(
|
|
|
35
35
|
default:
|
|
36
36
|
throw new Error('Unexpected response id');
|
|
37
37
|
}
|
|
38
|
-
}
|
|
38
|
+
},
|
|
39
|
+
cancel() {
|
|
40
|
+
throw new Error(`Sync commands don't support cancel.`);
|
|
41
|
+
},
|
|
39
42
|
}, {
|
|
40
43
|
highWaterMark: 16 * 1024,
|
|
41
44
|
size(chunk) { return chunk.byteLength; }
|
|
@@ -4,6 +4,7 @@ import { decodeUtf8 } from "../../utils/index.js";
|
|
|
4
4
|
|
|
5
5
|
export enum AdbSyncResponseId {
|
|
6
6
|
Entry = 'DENT',
|
|
7
|
+
Entry2 = 'DNT2',
|
|
7
8
|
Lstat = 'STAT',
|
|
8
9
|
Stat = 'STA2',
|
|
9
10
|
Lstat2 = 'LST2',
|
|
@@ -43,7 +44,12 @@ export const AdbSyncFailResponse =
|
|
|
43
44
|
export async function adbSyncReadResponse<T extends Record<string, StructLike<any>>>(
|
|
44
45
|
stream: AdbBufferedStream,
|
|
45
46
|
types: T,
|
|
46
|
-
|
|
47
|
+
// When `T` is a union type, `T[keyof T]` only includes their common keys.
|
|
48
|
+
// For example, let `type T = { a: string, b: string } | { a: string, c: string}`,
|
|
49
|
+
// `keyof T` is `'a'`, not `'a' | 'b' | 'c'`.
|
|
50
|
+
// However, `T extends unknown ? keyof T : never` will distribute `T`,
|
|
51
|
+
// so returns all keys.
|
|
52
|
+
): Promise<StructValueType<T extends unknown ? T[keyof T] : never>> {
|
|
47
53
|
const id = decodeUtf8(await stream.read(4));
|
|
48
54
|
|
|
49
55
|
if (id === AdbSyncResponseId.Fail) {
|
|
@@ -54,5 +60,5 @@ export async function adbSyncReadResponse<T extends Record<string, StructLike<an
|
|
|
54
60
|
return types[id]!.deserialize(stream);
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
throw new Error('
|
|
63
|
+
throw new Error(`Expected '${Object.keys(types).join(', ')}', but got '${id}'`);
|
|
58
64
|
}
|
|
@@ -10,6 +10,19 @@ export enum LinuxFileType {
|
|
|
10
10
|
Link = 0o12,
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface AdbSyncStat {
|
|
14
|
+
mode: number;
|
|
15
|
+
size: bigint;
|
|
16
|
+
mtime: bigint;
|
|
17
|
+
get type(): LinuxFileType;
|
|
18
|
+
get permission(): number;
|
|
19
|
+
|
|
20
|
+
uid?: number;
|
|
21
|
+
gid?: number;
|
|
22
|
+
atime?: bigint;
|
|
23
|
+
ctime?: bigint;
|
|
24
|
+
}
|
|
25
|
+
|
|
13
26
|
export const AdbSyncLstatResponse =
|
|
14
27
|
new Struct({ littleEndian: true })
|
|
15
28
|
.int32('mode')
|
|
@@ -32,6 +45,7 @@ export const AdbSyncLstatResponse =
|
|
|
32
45
|
export type AdbSyncLstatResponse = typeof AdbSyncLstatResponse['TDeserializeResult'];
|
|
33
46
|
|
|
34
47
|
export enum AdbSyncStatErrorCode {
|
|
48
|
+
SUCCESS = 0,
|
|
35
49
|
EACCES = 13,
|
|
36
50
|
EEXIST = 17,
|
|
37
51
|
EFAULT = 14,
|
|
@@ -80,15 +94,15 @@ export const AdbSyncStatResponse =
|
|
|
80
94
|
|
|
81
95
|
export type AdbSyncStatResponse = typeof AdbSyncStatResponse['TDeserializeResult'];
|
|
82
96
|
|
|
83
|
-
const
|
|
97
|
+
const STAT_RESPONSE_TYPES = {
|
|
84
98
|
[AdbSyncResponseId.Stat]: AdbSyncStatResponse,
|
|
85
99
|
};
|
|
86
100
|
|
|
87
|
-
const
|
|
101
|
+
const LSTAT_RESPONSE_TYPES = {
|
|
88
102
|
[AdbSyncResponseId.Lstat]: AdbSyncLstatResponse,
|
|
89
103
|
};
|
|
90
104
|
|
|
91
|
-
const
|
|
105
|
+
const LSTAT_V2_RESPONSE_TYPES = {
|
|
92
106
|
[AdbSyncResponseId.Lstat2]: AdbSyncStatResponse,
|
|
93
107
|
};
|
|
94
108
|
|
|
@@ -97,20 +111,33 @@ export async function adbSyncLstat(
|
|
|
97
111
|
writer: WritableStreamDefaultWriter<Uint8Array>,
|
|
98
112
|
path: string,
|
|
99
113
|
v2: boolean,
|
|
100
|
-
): Promise<
|
|
114
|
+
): Promise<AdbSyncStat> {
|
|
101
115
|
let requestId: AdbSyncRequestId.Lstat | AdbSyncRequestId.Lstat2;
|
|
102
|
-
let responseType: typeof
|
|
116
|
+
let responseType: typeof LSTAT_RESPONSE_TYPES | typeof LSTAT_V2_RESPONSE_TYPES;
|
|
103
117
|
|
|
104
118
|
if (v2) {
|
|
105
119
|
requestId = AdbSyncRequestId.Lstat2;
|
|
106
|
-
responseType =
|
|
120
|
+
responseType = LSTAT_V2_RESPONSE_TYPES;
|
|
107
121
|
} else {
|
|
108
122
|
requestId = AdbSyncRequestId.Lstat;
|
|
109
|
-
responseType =
|
|
123
|
+
responseType = LSTAT_RESPONSE_TYPES;
|
|
110
124
|
}
|
|
111
125
|
|
|
112
126
|
await adbSyncWriteRequest(writer, requestId, path);
|
|
113
|
-
|
|
127
|
+
const response = await adbSyncReadResponse(stream, responseType);
|
|
128
|
+
|
|
129
|
+
switch (response.id) {
|
|
130
|
+
case AdbSyncResponseId.Lstat:
|
|
131
|
+
return {
|
|
132
|
+
mode: response.mode,
|
|
133
|
+
size: BigInt(response.size),
|
|
134
|
+
mtime: BigInt(response.mtime),
|
|
135
|
+
get type() { return response.type; },
|
|
136
|
+
get permission() { return response.permission; },
|
|
137
|
+
};
|
|
138
|
+
default:
|
|
139
|
+
return response;
|
|
140
|
+
}
|
|
114
141
|
}
|
|
115
142
|
|
|
116
143
|
export async function adbSyncStat(
|
|
@@ -119,5 +146,5 @@ export async function adbSyncStat(
|
|
|
119
146
|
path: string,
|
|
120
147
|
): Promise<AdbSyncStatResponse> {
|
|
121
148
|
await adbSyncWriteRequest(writer, AdbSyncRequestId.Stat, path);
|
|
122
|
-
return await adbSyncReadResponse(stream,
|
|
149
|
+
return await adbSyncReadResponse(stream, STAT_RESPONSE_TYPES);
|
|
123
150
|
}
|
|
@@ -4,11 +4,28 @@ import { AdbFeatures } from '../../features.js';
|
|
|
4
4
|
import type { AdbSocket } from '../../socket/index.js';
|
|
5
5
|
import { AdbBufferedStream, ReadableStream, WrapReadableStream, WrapWritableStream, WritableStream, WritableStreamDefaultWriter } from '../../stream/index.js';
|
|
6
6
|
import { AutoResetEvent } from '../../utils/index.js';
|
|
7
|
-
import {
|
|
7
|
+
import { escapeArg } from "../index.js";
|
|
8
|
+
import { adbSyncOpenDir, type AdbSyncEntry } from './list.js';
|
|
8
9
|
import { adbSyncPull } from './pull.js';
|
|
9
10
|
import { adbSyncPush } from './push.js';
|
|
10
11
|
import { adbSyncLstat, adbSyncStat } from './stat.js';
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* A simplified `dirname` function that only handles absolute unix paths.
|
|
15
|
+
* @param path an absolute unix path
|
|
16
|
+
* @returns the directory name of the input path
|
|
17
|
+
*/
|
|
18
|
+
export function dirname(path: string): string {
|
|
19
|
+
const end = path.lastIndexOf('/');
|
|
20
|
+
if (end === -1) {
|
|
21
|
+
throw new Error(`Invalid path`);
|
|
22
|
+
}
|
|
23
|
+
if (end === 0) {
|
|
24
|
+
return '/';
|
|
25
|
+
}
|
|
26
|
+
return path.substring(0, end);
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
export class AdbSync extends AutoDisposable {
|
|
13
30
|
protected adb: Adb;
|
|
14
31
|
|
|
@@ -22,6 +39,19 @@ export class AdbSync extends AutoDisposable {
|
|
|
22
39
|
return this.adb.features!.includes(AdbFeatures.StatV2);
|
|
23
40
|
}
|
|
24
41
|
|
|
42
|
+
public get supportsList2(): boolean {
|
|
43
|
+
return this.adb.features!.includes(AdbFeatures.ListV2);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public get fixedPushMkdir(): boolean {
|
|
47
|
+
return this.adb.features!.includes(AdbFeatures.FixedPushMkdir);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public get needPushMkdirWorkaround(): boolean {
|
|
51
|
+
// https://android.googlesource.com/platform/packages/modules/adb/+/91768a57b7138166e0a3d11f79cd55909dda7014/client/file_sync_client.cpp#1361
|
|
52
|
+
return this.adb.features!.includes(AdbFeatures.ShellV2) && !this.fixedPushMkdir;
|
|
53
|
+
}
|
|
54
|
+
|
|
25
55
|
public constructor(adb: Adb, socket: AdbSocket) {
|
|
26
56
|
super();
|
|
27
57
|
|
|
@@ -65,18 +95,18 @@ export class AdbSync extends AutoDisposable {
|
|
|
65
95
|
|
|
66
96
|
public async *opendir(
|
|
67
97
|
path: string
|
|
68
|
-
): AsyncGenerator<
|
|
98
|
+
): AsyncGenerator<AdbSyncEntry, void, void> {
|
|
69
99
|
await this.sendLock.wait();
|
|
70
100
|
|
|
71
101
|
try {
|
|
72
|
-
yield* adbSyncOpenDir(this.stream, this.writer, path);
|
|
102
|
+
yield* adbSyncOpenDir(this.stream, this.writer, path, this.supportsList2);
|
|
73
103
|
} finally {
|
|
74
104
|
this.sendLock.notify();
|
|
75
105
|
}
|
|
76
106
|
}
|
|
77
107
|
|
|
78
108
|
public async readdir(path: string) {
|
|
79
|
-
const results:
|
|
109
|
+
const results: AdbSyncEntry[] = [];
|
|
80
110
|
for await (const entry of this.opendir(path)) {
|
|
81
111
|
results.push(entry);
|
|
82
112
|
}
|
|
@@ -90,13 +120,10 @@ export class AdbSync extends AutoDisposable {
|
|
|
90
120
|
* @returns A `ReadableStream` that reads from the file.
|
|
91
121
|
*/
|
|
92
122
|
public read(filename: string): ReadableStream<Uint8Array> {
|
|
93
|
-
return new WrapReadableStream
|
|
123
|
+
return new WrapReadableStream({
|
|
94
124
|
start: async () => {
|
|
95
125
|
await this.sendLock.wait();
|
|
96
|
-
return
|
|
97
|
-
readable: adbSyncPull(this.stream, this.writer, filename),
|
|
98
|
-
state: undefined,
|
|
99
|
-
};
|
|
126
|
+
return adbSyncPull(this.stream, this.writer, filename);
|
|
100
127
|
},
|
|
101
128
|
close: async () => {
|
|
102
129
|
this.sendLock.notify();
|
|
@@ -120,16 +147,25 @@ export class AdbSync extends AutoDisposable {
|
|
|
120
147
|
return new WrapWritableStream({
|
|
121
148
|
start: async () => {
|
|
122
149
|
await this.sendLock.wait();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
150
|
+
|
|
151
|
+
if (this.needPushMkdirWorkaround) {
|
|
152
|
+
// It may fail if the path is already existed.
|
|
153
|
+
// Ignore the result.
|
|
154
|
+
// TODO: sync: test this
|
|
155
|
+
await this.adb.subprocess.spawnAndWait([
|
|
156
|
+
'mkdir',
|
|
157
|
+
'-p',
|
|
158
|
+
escapeArg(dirname(filename)),
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return adbSyncPush(
|
|
163
|
+
this.stream,
|
|
164
|
+
this.writer,
|
|
165
|
+
filename,
|
|
166
|
+
mode,
|
|
167
|
+
mtime,
|
|
168
|
+
);
|
|
133
169
|
},
|
|
134
170
|
close: async () => {
|
|
135
171
|
this.sendLock.notify();
|