@yume-chan/adb 3.0.0-beta.0 → 3.0.0-beta.2
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/esm/adb.d.ts +7 -2
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +2 -2
- package/esm/adb.js.map +1 -1
- package/esm/banner.spec.d.ts +2 -0
- package/esm/banner.spec.d.ts.map +1 -0
- package/esm/daemon/auth/packet-processor.spec.d.ts +2 -0
- package/esm/daemon/auth/packet-processor.spec.d.ts.map +1 -0
- package/esm/daemon/auth/public-key.spec.d.ts +2 -0
- package/esm/daemon/auth/public-key.spec.d.ts.map +1 -0
- package/esm/daemon/crypto.spec.d.ts +2 -0
- package/esm/daemon/crypto.spec.d.ts.map +1 -0
- package/esm/server/client.d.ts +4 -3
- package/esm/server/client.d.ts.map +1 -1
- package/esm/server/client.js +13 -12
- package/esm/server/client.js.map +1 -1
- package/esm/server/client.spec.d.ts +2 -0
- package/esm/server/client.spec.d.ts.map +1 -0
- package/esm/server/transport.d.ts +1 -1
- package/esm/server/transport.d.ts.map +1 -1
- package/esm/server/transport.js +2 -2
- package/esm/server/transport.js.map +1 -1
- package/esm/service/reverse/response.spec.d.ts +2 -0
- package/esm/service/reverse/response.spec.d.ts.map +1 -0
- package/esm/service/subprocess/none/process.spec.d.ts +2 -0
- package/esm/service/subprocess/none/process.spec.d.ts.map +1 -0
- package/esm/service/subprocess/none/spawner.d.ts.map +1 -1
- package/esm/service/subprocess/none/spawner.js +10 -5
- package/esm/service/subprocess/none/spawner.js.map +1 -1
- package/esm/service/subprocess/none/spawner.spec.d.ts +2 -0
- package/esm/service/subprocess/none/spawner.spec.d.ts.map +1 -0
- package/esm/service/subprocess/shell/process.d.ts.map +1 -1
- package/esm/service/subprocess/shell/process.js +7 -9
- package/esm/service/subprocess/shell/process.js.map +1 -1
- package/esm/service/subprocess/shell/process.spec.d.ts +2 -0
- package/esm/service/subprocess/shell/process.spec.d.ts.map +1 -0
- package/esm/service/subprocess/shell/shared.spec.d.ts +2 -0
- package/esm/service/subprocess/shell/shared.spec.d.ts.map +1 -0
- package/esm/service/subprocess/shell/spawner.d.ts.map +1 -1
- package/esm/service/subprocess/shell/spawner.js +16 -11
- package/esm/service/subprocess/shell/spawner.js.map +1 -1
- package/esm/service/subprocess/shell/spawner.spec.d.ts +2 -0
- package/esm/service/subprocess/shell/spawner.spec.d.ts.map +1 -0
- package/esm/service/subprocess/utils.spec.d.ts +2 -0
- package/esm/service/subprocess/utils.spec.d.ts.map +1 -0
- package/esm/service/sync/compression/adapters.d.ts +16 -0
- package/esm/service/sync/compression/adapters.d.ts.map +1 -0
- package/esm/service/sync/compression/adapters.js +84 -0
- package/esm/service/sync/compression/adapters.js.map +1 -0
- package/esm/service/sync/compression/format-ns.d.ts.map +1 -1
- package/esm/service/sync/compression/format-ns.js +1 -0
- package/esm/service/sync/compression/format-ns.js.map +1 -1
- package/esm/service/sync/compression/format.d.ts +1 -0
- package/esm/service/sync/compression/format.d.ts.map +1 -1
- package/esm/service/sync/compression/format.js +1 -0
- package/esm/service/sync/compression/format.js.map +1 -1
- package/esm/service/sync/compression/index.d.ts +1 -1
- package/esm/service/sync/compression/index.d.ts.map +1 -1
- package/esm/service/sync/compression/index.js +1 -1
- package/esm/service/sync/compression/index.js.map +1 -1
- package/esm/service/sync/compression/mode.d.ts +6 -0
- package/esm/service/sync/compression/mode.d.ts.map +1 -0
- package/esm/service/sync/compression/mode.js +5 -0
- package/esm/service/sync/compression/mode.js.map +1 -0
- package/esm/service/sync/compression/native.d.ts +8 -0
- package/esm/service/sync/compression/native.d.ts.map +1 -0
- package/esm/service/sync/compression/native.js +53 -0
- package/esm/service/sync/compression/native.js.map +1 -0
- package/esm/service/sync/id/request.d.ts +1 -0
- package/esm/service/sync/id/request.d.ts.map +1 -1
- package/esm/service/sync/id/request.js +1 -0
- package/esm/service/sync/id/request.js.map +1 -1
- package/esm/service/sync/request/flag.d.ts +9 -0
- package/esm/service/sync/request/flag.d.ts.map +1 -0
- package/esm/service/sync/request/flag.js +8 -0
- package/esm/service/sync/request/flag.js.map +1 -0
- package/esm/service/sync/request/pull.d.ts +25 -2
- package/esm/service/sync/request/pull.d.ts.map +1 -1
- package/esm/service/sync/request/pull.js +76 -16
- package/esm/service/sync/request/pull.js.map +1 -1
- package/esm/service/sync/request/push.d.ts +36 -14
- package/esm/service/sync/request/push.d.ts.map +1 -1
- package/esm/service/sync/request/push.js +176 -60
- package/esm/service/sync/request/push.js.map +1 -1
- package/esm/service/sync/service.d.ts +32 -16
- package/esm/service/sync/service.d.ts.map +1 -1
- package/esm/service/sync/service.js +58 -13
- package/esm/service/sync/service.js.map +1 -1
- package/esm/service/sync/socket-pool.d.ts +1 -0
- package/esm/service/sync/socket-pool.d.ts.map +1 -1
- package/esm/service/sync/socket-pool.js +45 -9
- package/esm/service/sync/socket-pool.js.map +1 -1
- package/esm/service/sync/socket-pool.spec.d.ts +2 -0
- package/esm/service/sync/socket-pool.spec.d.ts.map +1 -0
- package/esm/service/sync/socket.d.ts +4 -0
- package/esm/service/sync/socket.d.ts.map +1 -1
- package/esm/service/sync/socket.js +16 -0
- package/esm/service/sync/socket.js.map +1 -1
- package/esm/service/sync/socket.spec.d.ts +2 -0
- package/esm/service/sync/socket.spec.d.ts.map +1 -0
- package/esm/utils/base64.spec.d.ts +2 -0
- package/esm/utils/base64.spec.d.ts.map +1 -0
- package/esm/utils/md5.spec.d.ts +2 -0
- package/esm/utils/md5.spec.d.ts.map +1 -0
- package/esm/utils/ref.d.ts +3 -5
- package/esm/utils/ref.d.ts.map +1 -1
- package/esm/utils/ref.js +12 -6
- package/esm/utils/ref.js.map +1 -1
- package/package.json +7 -7
- package/src/adb.ts +15 -3
- package/src/server/client.ts +18 -11
- package/src/server/transport.ts +5 -1
- package/src/service/subprocess/none/spawner.ts +9 -5
- package/src/service/subprocess/shell/process.ts +7 -10
- package/src/service/subprocess/shell/spawner.ts +15 -11
- package/src/service/sync/compression/adapters.ts +137 -0
- package/src/service/sync/compression/format-ns.ts +2 -0
- package/src/service/sync/compression/format.ts +5 -0
- package/src/service/sync/compression/index.ts +1 -1
- package/src/service/sync/compression/mode.ts +6 -0
- package/src/service/sync/compression/native.ts +73 -0
- package/src/service/sync/id/request.ts +1 -0
- package/src/service/sync/request/flag.ts +9 -0
- package/src/service/sync/request/pull.ts +135 -27
- package/src/service/sync/request/push.ts +241 -91
- package/src/service/sync/service.ts +87 -27
- package/src/service/sync/socket-pool.ts +50 -9
- package/src/service/sync/socket.ts +23 -1
- package/src/utils/ref.ts +17 -7
- package/esm/service/sync/compression/compression.d.ts +0 -17
- package/esm/service/sync/compression/compression.d.ts.map +0 -1
- package/esm/service/sync/compression/compression.js +0 -92
- package/esm/service/sync/compression/compression.js.map +0 -1
- package/src/service/sync/compression/compression.ts +0 -126
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CompressionStream,
|
|
3
|
+
DecompressionStream,
|
|
4
|
+
TransformStream,
|
|
5
|
+
} from "@yume-chan/stream-extra";
|
|
6
|
+
|
|
7
|
+
import { NOOP } from "../../../utils/no-op.js";
|
|
8
|
+
|
|
9
|
+
import { Format } from "./format.js";
|
|
10
|
+
import { Mode } from "./mode.js";
|
|
11
|
+
|
|
12
|
+
export const FormatMap: Record<Format, string> = {
|
|
13
|
+
[Format.None]: "",
|
|
14
|
+
// https://github.com/whatwg/compression/issues/54
|
|
15
|
+
[Format.Zstd]: "zstd",
|
|
16
|
+
// placeholder
|
|
17
|
+
[Format.Lz4]: "lz4",
|
|
18
|
+
// https://github.com/whatwg/compression/pull/80
|
|
19
|
+
[Format.Brotli]: "brotli",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const CompressionFormatCache = new Map<Format, boolean>();
|
|
23
|
+
const DecompressionFormatCache = new Map<Format, boolean>();
|
|
24
|
+
|
|
25
|
+
export function hasNativeSupport(format: Format, mode: Mode) {
|
|
26
|
+
if (format === Format.None) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const Cache =
|
|
31
|
+
mode === Mode.Compress
|
|
32
|
+
? CompressionFormatCache
|
|
33
|
+
: DecompressionFormatCache;
|
|
34
|
+
if (Cache.has(format)) {
|
|
35
|
+
return Cache.get(format)!;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const Stream =
|
|
39
|
+
mode === Mode.Compress ? CompressionStream : DecompressionStream;
|
|
40
|
+
if (!Stream) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const stream = new Stream(FormatMap[format]);
|
|
46
|
+
void stream.writable.abort().catch(NOOP);
|
|
47
|
+
Cache.set(format, true);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
Cache.set(format, false);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createNativeCompressionStream(
|
|
56
|
+
format: Format,
|
|
57
|
+
): CompressionStream {
|
|
58
|
+
if (format === Format.None) {
|
|
59
|
+
return new TransformStream();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return new CompressionStream(FormatMap[format]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createNativeDecompressionStream(
|
|
66
|
+
format: Format,
|
|
67
|
+
): DecompressionStream {
|
|
68
|
+
if (format === Format.None) {
|
|
69
|
+
return new TransformStream();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return new DecompressionStream(FormatMap[format]);
|
|
73
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TransformStream } from "@yume-chan/stream-extra";
|
|
2
|
+
import { InspectStream, ReadableStream } from "@yume-chan/stream-extra";
|
|
2
3
|
import type { StructValue } from "@yume-chan/struct";
|
|
3
4
|
import { buffer, struct, u32 } from "@yume-chan/struct";
|
|
4
5
|
|
|
5
6
|
import { RequestId, ResponseId } from "../id/index.js";
|
|
7
|
+
import { Compression } from "../index-ns.js";
|
|
6
8
|
import type { SocketPool } from "../socket-pool.js";
|
|
7
9
|
import { Error as AdbSyncError } from "../socket.js";
|
|
8
10
|
|
|
11
|
+
import { SyncFlag } from "./flag.js";
|
|
12
|
+
|
|
9
13
|
export const DataResponse = struct(
|
|
10
14
|
{ data: buffer(u32) },
|
|
11
15
|
{ littleEndian: true },
|
|
@@ -13,37 +17,141 @@ export const DataResponse = struct(
|
|
|
13
17
|
|
|
14
18
|
export type DataResponse = StructValue<typeof DataResponse>;
|
|
15
19
|
|
|
16
|
-
export
|
|
20
|
+
export interface ReceiveSession {
|
|
21
|
+
/**
|
|
22
|
+
* The readable stream to read the file content from.
|
|
23
|
+
*/
|
|
24
|
+
readable: ReadableStream<Uint8Array>;
|
|
25
|
+
/**
|
|
26
|
+
* Gets the number of bytes read from {@link readable}.
|
|
27
|
+
*/
|
|
28
|
+
bytesRead: number;
|
|
29
|
+
/**
|
|
30
|
+
* When using Pull v2, gets the compression format used (might be `None`).
|
|
31
|
+
*
|
|
32
|
+
* When using Pull v1, this will always be `undefined`.
|
|
33
|
+
*/
|
|
34
|
+
compression?: Compression.Format | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* When using Pull v2, gets the size of the compressed data received from the device.
|
|
37
|
+
* Might be same as {@link bytesRead} if no compression was applied.
|
|
38
|
+
*/
|
|
39
|
+
bytesCompressed: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function receiveV1(pool: SocketPool, path: string): ReceiveSession {
|
|
43
|
+
let bytesReceived = 0;
|
|
44
|
+
return {
|
|
45
|
+
readable: ReadableStream.from(
|
|
46
|
+
pool.withSocketGenerator(async function* (socket) {
|
|
47
|
+
await socket.writeRequest(RequestId.Receive, path);
|
|
48
|
+
|
|
49
|
+
for await (const packet of socket.readResponses(
|
|
50
|
+
ResponseId.Data,
|
|
51
|
+
DataResponse,
|
|
52
|
+
)) {
|
|
53
|
+
bytesReceived += packet.data.length;
|
|
54
|
+
yield packet.data;
|
|
55
|
+
}
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
get bytesRead() {
|
|
59
|
+
return bytesReceived;
|
|
60
|
+
},
|
|
61
|
+
compression: undefined,
|
|
62
|
+
get bytesCompressed() {
|
|
63
|
+
return bytesReceived;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function receiveV2(
|
|
17
69
|
pool: SocketPool,
|
|
18
70
|
path: string,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
71
|
+
compression?: Compression.Format,
|
|
72
|
+
): ReceiveSession {
|
|
73
|
+
let flags: SyncFlag = SyncFlag.None;
|
|
74
|
+
let decompressStream: TransformStream<Uint8Array, Uint8Array> | undefined;
|
|
75
|
+
switch (compression) {
|
|
76
|
+
case Compression.Format.Brotli:
|
|
77
|
+
flags |= SyncFlag.Brotli;
|
|
78
|
+
decompressStream = Compression.createDecompressionStream(
|
|
79
|
+
Compression.Format.Brotli,
|
|
80
|
+
);
|
|
81
|
+
break;
|
|
82
|
+
case Compression.Format.Lz4:
|
|
83
|
+
flags |= SyncFlag.Lz4;
|
|
84
|
+
decompressStream = Compression.createDecompressionStream(
|
|
85
|
+
Compression.Format.Lz4,
|
|
86
|
+
);
|
|
87
|
+
break;
|
|
88
|
+
case Compression.Format.Zstd:
|
|
89
|
+
flags |= SyncFlag.Zstd;
|
|
90
|
+
decompressStream = Compression.createDecompressionStream(
|
|
91
|
+
Compression.Format.Zstd,
|
|
92
|
+
);
|
|
93
|
+
break;
|
|
41
94
|
}
|
|
95
|
+
|
|
96
|
+
let bytesReceived = 0;
|
|
97
|
+
const raw = ReadableStream.from(
|
|
98
|
+
pool.withSocketGenerator(async function* (socket) {
|
|
99
|
+
await socket.writeRequest(RequestId.ReceiveV2, path);
|
|
100
|
+
await socket.writeRequest(RequestId.ReceiveV2, flags);
|
|
101
|
+
|
|
102
|
+
for await (const packet of socket.readResponses(
|
|
103
|
+
ResponseId.Data,
|
|
104
|
+
DataResponse,
|
|
105
|
+
)) {
|
|
106
|
+
bytesReceived += packet.data.length;
|
|
107
|
+
yield packet.data;
|
|
108
|
+
}
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (decompressStream) {
|
|
113
|
+
let bytesRead = 0;
|
|
114
|
+
return {
|
|
115
|
+
readable: raw.pipeThrough(decompressStream).pipeThrough(
|
|
116
|
+
new InspectStream((chunk) => {
|
|
117
|
+
bytesRead += chunk.length;
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
get bytesRead() {
|
|
121
|
+
return bytesRead;
|
|
122
|
+
},
|
|
123
|
+
compression,
|
|
124
|
+
get bytesCompressed() {
|
|
125
|
+
return bytesReceived;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
readable: raw,
|
|
132
|
+
get bytesRead() {
|
|
133
|
+
return bytesReceived;
|
|
134
|
+
},
|
|
135
|
+
compression: Compression.Format.None,
|
|
136
|
+
get bytesCompressed() {
|
|
137
|
+
return bytesReceived;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
42
140
|
}
|
|
43
141
|
|
|
44
|
-
export function
|
|
142
|
+
export function receive(
|
|
143
|
+
version: 1 | 2,
|
|
45
144
|
pool: SocketPool,
|
|
46
145
|
path: string,
|
|
47
|
-
|
|
48
|
-
|
|
146
|
+
compression?: Compression.Format,
|
|
147
|
+
) {
|
|
148
|
+
if (version === 2) {
|
|
149
|
+
return receiveV2(pool, path, compression);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (compression !== undefined && compression !== Compression.Format.None) {
|
|
153
|
+
throw new AdbSyncError("compression is not supported in v1");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return receiveV1(pool, path);
|
|
49
157
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { PromiseResolver } from "@yume-chan/async";
|
|
2
|
+
import type {
|
|
3
|
+
TransformStream,
|
|
4
|
+
WritableStream,
|
|
5
|
+
WritableStreamDefaultController,
|
|
6
6
|
} from "@yume-chan/stream-extra";
|
|
7
|
+
import { DistributionStream, MaybeConsumable } from "@yume-chan/stream-extra";
|
|
7
8
|
import { struct, u32 } from "@yume-chan/struct";
|
|
8
9
|
|
|
9
|
-
import { NOOP } from "../../../utils/
|
|
10
|
+
import { NOOP } from "../../../utils/no-op.js";
|
|
10
11
|
import { LinuxFileType } from "../android.js";
|
|
11
12
|
import { Compression } from "../compression/index.js";
|
|
12
13
|
import { RequestId, ResponseId } from "../id/index.js";
|
|
@@ -14,93 +15,166 @@ import type { SocketPool } from "../socket-pool.js";
|
|
|
14
15
|
import type { Socket } from "../socket.js";
|
|
15
16
|
import { Error as AdbSyncError } from "../socket.js";
|
|
16
17
|
|
|
18
|
+
import { SyncFlag } from "./flag.js";
|
|
19
|
+
|
|
17
20
|
export const MaxPacketSize = 64 * 1024;
|
|
18
21
|
|
|
19
22
|
export const OkResponse = struct({ unused: u32 }, { littleEndian: true });
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
export interface SendSession {
|
|
25
|
+
/**
|
|
26
|
+
* The writable stream to write the file content into.
|
|
27
|
+
*/
|
|
28
|
+
writable: WritableStream<MaybeConsumable<Uint8Array>>;
|
|
29
|
+
/**
|
|
30
|
+
* Gets the number of bytes written into {@link writable}.
|
|
31
|
+
*/
|
|
32
|
+
bytesWritten: number;
|
|
33
|
+
/**
|
|
34
|
+
* When using Send v2, gets the compression format used (might be `None`).
|
|
35
|
+
*
|
|
36
|
+
* When using Send v1, this will always be `undefined`.
|
|
37
|
+
*/
|
|
38
|
+
compression?: Compression.Format | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* When using Send v2, gets the size of the compressed data sent to the device.
|
|
41
|
+
* Might be same as {@link bytesWritten} if no compression was applied.
|
|
42
|
+
*/
|
|
43
|
+
bytesCompressed: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class SendWritableStream extends MaybeConsumable.WritableStream<Uint8Array> {
|
|
47
|
+
#pool: SocketPool;
|
|
48
|
+
#socket: Socket;
|
|
49
|
+
|
|
50
|
+
#resolver = new PromiseResolver<void>();
|
|
51
|
+
#controller: WritableStreamDefaultController;
|
|
52
|
+
|
|
53
|
+
#bytesWritten = 0;
|
|
54
|
+
get bytesWritten() {
|
|
55
|
+
return this.#bytesWritten;
|
|
35
56
|
}
|
|
36
57
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const abortController = new AbortController();
|
|
40
|
-
file.pipeThrough(new DistributionStream(packetSize, true))
|
|
41
|
-
.pipeTo(
|
|
42
|
-
new MaybeConsumable.WritableStream({
|
|
43
|
-
write(chunk) {
|
|
44
|
-
return socket.writeRequest(RequestId.Data, chunk);
|
|
45
|
-
},
|
|
46
|
-
}),
|
|
47
|
-
{ signal: abortController.signal },
|
|
48
|
-
)
|
|
49
|
-
.then(async () => {
|
|
50
|
-
await socket.writeRequest(RequestId.Done, mtime);
|
|
51
|
-
await socket.flush();
|
|
52
|
-
}, NOOP);
|
|
58
|
+
constructor(pool: SocketPool, socket: Socket, mtime: number) {
|
|
59
|
+
let controller!: WritableStreamDefaultController;
|
|
53
60
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
super({
|
|
62
|
+
start: (controller_) => {
|
|
63
|
+
controller = controller_;
|
|
64
|
+
|
|
65
|
+
// Start reading response immediately,
|
|
66
|
+
// the server can send error response before the whole file is sent.
|
|
67
|
+
socket.readResponse(ResponseId.Ok, OkResponse).then(
|
|
68
|
+
() => this.#finish(),
|
|
69
|
+
(e) => this.#finish(e),
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
write: async (chunk) => {
|
|
73
|
+
try {
|
|
74
|
+
this.#bytesWritten += chunk.length;
|
|
75
|
+
await socket.writeRequest(RequestId.Data, chunk);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
await this.#finish(e);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
close: async () => {
|
|
81
|
+
try {
|
|
82
|
+
await socket.writeRequest(RequestId.Done, mtime);
|
|
83
|
+
await socket.flush();
|
|
84
|
+
} catch (e) {
|
|
85
|
+
await this.#finish(e);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await this.#resolver.promise;
|
|
89
|
+
},
|
|
90
|
+
abort: async (reason) => {
|
|
91
|
+
// Write anything other than `Data` or `Done`
|
|
92
|
+
// will cause the server to delete the file.
|
|
93
|
+
await socket.writeRequest(ResponseId.Fail, 0);
|
|
94
|
+
|
|
95
|
+
// Socket not reusable after aborting, so discard it.
|
|
96
|
+
await this.#finish(reason);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this.#pool = pool;
|
|
101
|
+
this.#socket = socket;
|
|
102
|
+
|
|
103
|
+
this.#controller = controller;
|
|
104
|
+
// Suppress unhandled rejection warning when the promise is not awaited.
|
|
105
|
+
this.#resolver.promise.catch(NOOP);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#trySetError(reason: unknown) {
|
|
109
|
+
try {
|
|
110
|
+
this.#controller.error(reason);
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore if controller is already closed or errored
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async #finish(error?: unknown) {
|
|
117
|
+
try {
|
|
118
|
+
await this.#pool.release(
|
|
119
|
+
this.#socket,
|
|
120
|
+
!(error instanceof AdbSyncError),
|
|
121
|
+
);
|
|
122
|
+
if (error) {
|
|
123
|
+
this.#trySetError(error);
|
|
124
|
+
this.#resolver.reject(error);
|
|
125
|
+
} else {
|
|
126
|
+
this.#resolver.resolve();
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {
|
|
129
|
+
// TOOD: use `SuppressedError` when universally supported
|
|
130
|
+
this.#trySetError(e);
|
|
131
|
+
this.#resolver.reject(e);
|
|
132
|
+
}
|
|
59
133
|
}
|
|
60
134
|
}
|
|
61
135
|
|
|
62
136
|
export interface SendV1Options {
|
|
63
137
|
pool: SocketPool;
|
|
64
|
-
|
|
65
|
-
file: ReadableStream<MaybeConsumable<Uint8Array>>;
|
|
138
|
+
path: string;
|
|
66
139
|
type?: LinuxFileType | undefined;
|
|
67
140
|
permission?: number | undefined;
|
|
68
141
|
mtime?: number | undefined;
|
|
69
142
|
packetSize?: number | undefined;
|
|
70
143
|
}
|
|
71
144
|
|
|
72
|
-
export function sendV1({
|
|
145
|
+
export async function sendV1({
|
|
73
146
|
pool,
|
|
74
|
-
|
|
75
|
-
file,
|
|
147
|
+
path,
|
|
76
148
|
type = LinuxFileType.File,
|
|
77
149
|
permission = 0o666,
|
|
78
150
|
mtime = (Date.now() / 1000) | 0,
|
|
79
151
|
packetSize = MaxPacketSize,
|
|
80
|
-
}: SendV1Options) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
);
|
|
92
|
-
});
|
|
93
|
-
}
|
|
152
|
+
}: SendV1Options): Promise<SendSession> {
|
|
153
|
+
const mode = (type << 12) | permission;
|
|
154
|
+
const request = path + "," + mode;
|
|
155
|
+
|
|
156
|
+
const socket = await pool.acquire();
|
|
157
|
+
try {
|
|
158
|
+
await socket.writeRequest(RequestId.Send, request);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
await pool.release(socket, !(e instanceof AdbSyncError));
|
|
161
|
+
throw e;
|
|
162
|
+
}
|
|
94
163
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Lz4: 2,
|
|
99
|
-
Zstd: 4,
|
|
100
|
-
DryRun: 0x80000000,
|
|
101
|
-
} as const;
|
|
164
|
+
const distributeStream = new DistributionStream(packetSize, true);
|
|
165
|
+
const sendStream = new SendWritableStream(pool, socket, mtime);
|
|
166
|
+
void distributeStream.readable.pipeTo(sendStream).catch(NOOP);
|
|
102
167
|
|
|
103
|
-
|
|
168
|
+
return {
|
|
169
|
+
writable: distributeStream.writable,
|
|
170
|
+
get bytesWritten() {
|
|
171
|
+
return sendStream.bytesWritten;
|
|
172
|
+
},
|
|
173
|
+
get bytesCompressed() {
|
|
174
|
+
return sendStream.bytesWritten;
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
104
178
|
|
|
105
179
|
export const SendV2Request = struct(
|
|
106
180
|
{ id: u32, mode: u32, flags: u32 },
|
|
@@ -108,6 +182,16 @@ export const SendV2Request = struct(
|
|
|
108
182
|
);
|
|
109
183
|
|
|
110
184
|
export interface SendV2Options extends SendV1Options {
|
|
185
|
+
/**
|
|
186
|
+
* The format to compress the file stream for sending.
|
|
187
|
+
*
|
|
188
|
+
* If the device or current runtime doesn't support the specified format,
|
|
189
|
+
* an Error will be thrown.
|
|
190
|
+
*
|
|
191
|
+
* If `undefined` is specified, no compression will be used.
|
|
192
|
+
* (this behavior is different from `AdbSync.Service.prototype.write`,
|
|
193
|
+
* which will automatically choose the best format)
|
|
194
|
+
*/
|
|
111
195
|
compression?: Compression.Format | undefined;
|
|
112
196
|
|
|
113
197
|
/**
|
|
@@ -119,35 +203,47 @@ export interface SendV2Options extends SendV1Options {
|
|
|
119
203
|
dryRun?: boolean | undefined;
|
|
120
204
|
}
|
|
121
205
|
|
|
122
|
-
export function sendV2({
|
|
206
|
+
export async function sendV2({
|
|
123
207
|
pool,
|
|
124
|
-
|
|
125
|
-
file,
|
|
208
|
+
path,
|
|
126
209
|
type = LinuxFileType.File,
|
|
127
210
|
permission = 0o666,
|
|
128
211
|
mtime = (Date.now() / 1000) | 0,
|
|
129
212
|
packetSize = MaxPacketSize,
|
|
130
213
|
compression = Compression.Format.None,
|
|
131
214
|
dryRun = false,
|
|
132
|
-
}: SendV2Options) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
215
|
+
}: SendV2Options): Promise<SendSession> {
|
|
216
|
+
let flags: SyncFlag = SyncFlag.None;
|
|
217
|
+
let compressStream: TransformStream<Uint8Array, Uint8Array> | undefined;
|
|
218
|
+
|
|
219
|
+
// Validate `compression` before acquiring the socket
|
|
220
|
+
switch (compression) {
|
|
221
|
+
case Compression.Format.Brotli:
|
|
222
|
+
flags |= SyncFlag.Brotli;
|
|
223
|
+
compressStream = Compression.createCompressionStream(
|
|
224
|
+
Compression.Format.Brotli,
|
|
225
|
+
);
|
|
226
|
+
break;
|
|
227
|
+
case Compression.Format.Lz4:
|
|
228
|
+
flags |= SyncFlag.Lz4;
|
|
229
|
+
compressStream = Compression.createCompressionStream(
|
|
230
|
+
Compression.Format.Lz4,
|
|
231
|
+
);
|
|
232
|
+
break;
|
|
233
|
+
case Compression.Format.Zstd:
|
|
234
|
+
flags |= SyncFlag.Zstd;
|
|
235
|
+
compressStream = Compression.createCompressionStream(
|
|
236
|
+
Compression.Format.Zstd,
|
|
237
|
+
);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
if (dryRun) {
|
|
241
|
+
flags |= SyncFlag.DryRun;
|
|
242
|
+
}
|
|
149
243
|
|
|
150
|
-
|
|
244
|
+
const socket = await pool.acquire();
|
|
245
|
+
try {
|
|
246
|
+
await socket.writeRequest(RequestId.SendV2, path);
|
|
151
247
|
await socket.write(
|
|
152
248
|
SendV2Request.serialize({
|
|
153
249
|
id: RequestId.SendV2,
|
|
@@ -155,9 +251,63 @@ export function sendV2({
|
|
|
155
251
|
flags,
|
|
156
252
|
}),
|
|
157
253
|
);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
await pool.release(socket, !(e instanceof AdbSyncError));
|
|
256
|
+
throw e;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const distributeStream = new DistributionStream(packetSize, true);
|
|
260
|
+
const sendStream = new SendWritableStream(pool, socket, mtime);
|
|
261
|
+
|
|
262
|
+
if (!compressStream) {
|
|
263
|
+
const pipe = distributeStream.readable.pipeTo(sendStream);
|
|
264
|
+
|
|
265
|
+
const writer = distributeStream.writable.getWriter();
|
|
266
|
+
return {
|
|
267
|
+
writable: new MaybeConsumable.WritableStream({
|
|
268
|
+
write(chunk) {
|
|
269
|
+
return writer.write(chunk);
|
|
270
|
+
},
|
|
271
|
+
async close() {
|
|
272
|
+
await writer.close();
|
|
273
|
+
await pipe;
|
|
274
|
+
},
|
|
275
|
+
}),
|
|
276
|
+
get bytesWritten() {
|
|
277
|
+
return sendStream.bytesWritten;
|
|
278
|
+
},
|
|
279
|
+
compression: Compression.Format.None,
|
|
280
|
+
get bytesCompressed() {
|
|
281
|
+
return sendStream.bytesWritten;
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const pipe = compressStream.readable
|
|
287
|
+
.pipeThrough(distributeStream)
|
|
288
|
+
.pipeTo(sendStream);
|
|
158
289
|
|
|
159
|
-
|
|
160
|
-
|
|
290
|
+
const writer = compressStream.writable.getWriter();
|
|
291
|
+
let bytesWritten = 0;
|
|
292
|
+
return {
|
|
293
|
+
writable: new MaybeConsumable.WritableStream({
|
|
294
|
+
write(chunk) {
|
|
295
|
+
bytesWritten += chunk.length;
|
|
296
|
+
return writer.write(chunk);
|
|
297
|
+
},
|
|
298
|
+
async close() {
|
|
299
|
+
await writer.close();
|
|
300
|
+
await pipe;
|
|
301
|
+
},
|
|
302
|
+
}),
|
|
303
|
+
get bytesWritten() {
|
|
304
|
+
return bytesWritten;
|
|
305
|
+
},
|
|
306
|
+
compression,
|
|
307
|
+
get bytesCompressed() {
|
|
308
|
+
return sendStream.bytesWritten;
|
|
309
|
+
},
|
|
310
|
+
};
|
|
161
311
|
}
|
|
162
312
|
|
|
163
313
|
export interface SendOptions extends SendV2Options {
|