@yume-chan/adb 0.0.23 → 1.0.0

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.
Files changed (178) hide show
  1. package/CHANGELOG.md +51 -2
  2. package/LICENSE +1 -1
  3. package/README.md +4 -1
  4. package/esm/adb.d.ts +15 -9
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +5 -2
  7. package/esm/adb.js.map +1 -1
  8. package/esm/banner.d.ts +7 -6
  9. package/esm/banner.d.ts.map +1 -1
  10. package/esm/banner.js +6 -7
  11. package/esm/banner.js.map +1 -1
  12. package/esm/commands/framebuffer.d.ts +63 -34
  13. package/esm/commands/framebuffer.d.ts.map +1 -1
  14. package/esm/commands/framebuffer.js +35 -33
  15. package/esm/commands/framebuffer.js.map +1 -1
  16. package/esm/commands/power.d.ts.map +1 -1
  17. package/esm/commands/power.js +6 -5
  18. package/esm/commands/power.js.map +1 -1
  19. package/esm/commands/reverse.d.ts +12 -12
  20. package/esm/commands/reverse.d.ts.map +1 -1
  21. package/esm/commands/reverse.js +60 -39
  22. package/esm/commands/reverse.js.map +1 -1
  23. package/esm/commands/subprocess/command.js +3 -3
  24. package/esm/commands/subprocess/command.js.map +1 -1
  25. package/esm/commands/subprocess/protocols/none.d.ts +2 -2
  26. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  27. package/esm/commands/subprocess/protocols/none.js +3 -5
  28. package/esm/commands/subprocess/protocols/none.js.map +1 -1
  29. package/esm/commands/subprocess/protocols/shell.d.ts +22 -14
  30. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
  31. package/esm/commands/subprocess/protocols/shell.js +21 -27
  32. package/esm/commands/subprocess/protocols/shell.js.map +1 -1
  33. package/esm/commands/subprocess/protocols/types.d.ts +8 -8
  34. package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
  35. package/esm/commands/sync/list.d.ts +49 -30
  36. package/esm/commands/sync/list.d.ts.map +1 -1
  37. package/esm/commands/sync/list.js +9 -11
  38. package/esm/commands/sync/list.js.map +1 -1
  39. package/esm/commands/sync/pull.d.ts +7 -9
  40. package/esm/commands/sync/pull.d.ts.map +1 -1
  41. package/esm/commands/sync/pull.js +4 -6
  42. package/esm/commands/sync/pull.js.map +1 -1
  43. package/esm/commands/sync/push.d.ts +25 -22
  44. package/esm/commands/sync/push.d.ts.map +1 -1
  45. package/esm/commands/sync/push.js +14 -21
  46. package/esm/commands/sync/push.js.map +1 -1
  47. package/esm/commands/sync/request.d.ts +20 -23
  48. package/esm/commands/sync/request.d.ts.map +1 -1
  49. package/esm/commands/sync/request.js +26 -35
  50. package/esm/commands/sync/request.js.map +1 -1
  51. package/esm/commands/sync/response.d.ts +25 -19
  52. package/esm/commands/sync/response.d.ts.map +1 -1
  53. package/esm/commands/sync/response.js +49 -26
  54. package/esm/commands/sync/response.js.map +1 -1
  55. package/esm/commands/sync/socket.d.ts +3 -3
  56. package/esm/commands/sync/socket.d.ts.map +1 -1
  57. package/esm/commands/sync/socket.js +10 -5
  58. package/esm/commands/sync/socket.js.map +1 -1
  59. package/esm/commands/sync/stat.d.ts +75 -54
  60. package/esm/commands/sync/stat.d.ts.map +1 -1
  61. package/esm/commands/sync/stat.js +73 -69
  62. package/esm/commands/sync/stat.js.map +1 -1
  63. package/esm/commands/sync/sync.d.ts +18 -17
  64. package/esm/commands/sync/sync.d.ts.map +1 -1
  65. package/esm/commands/sync/sync.js +8 -8
  66. package/esm/commands/sync/sync.js.map +1 -1
  67. package/esm/commands/tcpip.d.ts +0 -1
  68. package/esm/commands/tcpip.d.ts.map +1 -1
  69. package/esm/commands/tcpip.js +9 -9
  70. package/esm/commands/tcpip.js.map +1 -1
  71. package/esm/daemon/auth.d.ts +8 -7
  72. package/esm/daemon/auth.d.ts.map +1 -1
  73. package/esm/daemon/auth.js +8 -9
  74. package/esm/daemon/auth.js.map +1 -1
  75. package/esm/daemon/crypto.d.ts +4 -3
  76. package/esm/daemon/crypto.d.ts.map +1 -1
  77. package/esm/daemon/crypto.js +57 -49
  78. package/esm/daemon/crypto.js.map +1 -1
  79. package/esm/daemon/device.d.ts +2 -2
  80. package/esm/daemon/device.d.ts.map +1 -1
  81. package/esm/daemon/dispatcher.d.ts +35 -10
  82. package/esm/daemon/dispatcher.d.ts.map +1 -1
  83. package/esm/daemon/dispatcher.js +46 -33
  84. package/esm/daemon/dispatcher.js.map +1 -1
  85. package/esm/daemon/packet.d.ts +48 -32
  86. package/esm/daemon/packet.d.ts.map +1 -1
  87. package/esm/daemon/packet.js +36 -32
  88. package/esm/daemon/packet.js.map +1 -1
  89. package/esm/daemon/socket.d.ts +11 -11
  90. package/esm/daemon/socket.d.ts.map +1 -1
  91. package/esm/daemon/socket.js +19 -40
  92. package/esm/daemon/socket.js.map +1 -1
  93. package/esm/daemon/transport.d.ts +60 -20
  94. package/esm/daemon/transport.d.ts.map +1 -1
  95. package/esm/daemon/transport.js +12 -15
  96. package/esm/daemon/transport.js.map +1 -1
  97. package/esm/device-observer.d.ts +10 -0
  98. package/esm/device-observer.d.ts.map +1 -0
  99. package/esm/device-observer.js +2 -0
  100. package/esm/device-observer.js.map +1 -0
  101. package/esm/features.d.ts +12 -11
  102. package/esm/features.d.ts.map +1 -1
  103. package/esm/features.js +11 -12
  104. package/esm/features.js.map +1 -1
  105. package/esm/index.d.ts +1 -0
  106. package/esm/index.d.ts.map +1 -1
  107. package/esm/index.js +1 -0
  108. package/esm/index.js.map +1 -1
  109. package/esm/server/client.d.ts +140 -50
  110. package/esm/server/client.d.ts.map +1 -1
  111. package/esm/server/client.js +396 -180
  112. package/esm/server/client.js.map +1 -1
  113. package/esm/server/transport.d.ts +2 -2
  114. package/esm/server/transport.d.ts.map +1 -1
  115. package/esm/server/transport.js +4 -6
  116. package/esm/server/transport.js.map +1 -1
  117. package/esm/utils/base64.d.ts.map +1 -1
  118. package/esm/utils/base64.js +52 -38
  119. package/esm/utils/base64.js.map +1 -1
  120. package/esm/utils/hex.d.ts +1 -1
  121. package/esm/utils/hex.d.ts.map +1 -1
  122. package/esm/utils/hex.js +12 -14
  123. package/esm/utils/hex.js.map +1 -1
  124. package/esm/utils/index.d.ts +1 -1
  125. package/esm/utils/index.d.ts.map +1 -1
  126. package/esm/utils/index.js +1 -1
  127. package/esm/utils/index.js.map +1 -1
  128. package/esm/utils/no-op.d.ts.map +1 -1
  129. package/esm/utils/no-op.js +1 -0
  130. package/esm/utils/no-op.js.map +1 -1
  131. package/esm/utils/sequence-equal.d.ts +2 -0
  132. package/esm/utils/sequence-equal.d.ts.map +1 -0
  133. package/esm/utils/sequence-equal.js +12 -0
  134. package/esm/utils/sequence-equal.js.map +1 -0
  135. package/package.json +14 -19
  136. package/src/adb.ts +20 -11
  137. package/src/banner.ts +8 -6
  138. package/src/commands/framebuffer.ts +44 -35
  139. package/src/commands/power.ts +7 -5
  140. package/src/commands/reverse.ts +78 -47
  141. package/src/commands/subprocess/command.ts +3 -3
  142. package/src/commands/subprocess/protocols/none.ts +5 -7
  143. package/src/commands/subprocess/protocols/shell.ts +33 -35
  144. package/src/commands/subprocess/protocols/types.ts +8 -8
  145. package/src/commands/sync/list.ts +20 -15
  146. package/src/commands/sync/pull.ts +9 -8
  147. package/src/commands/sync/push.ts +25 -24
  148. package/src/commands/sync/request.ts +37 -38
  149. package/src/commands/sync/response.ts +63 -37
  150. package/src/commands/sync/socket.ts +13 -8
  151. package/src/commands/sync/stat.ts +93 -73
  152. package/src/commands/sync/sync.ts +12 -11
  153. package/src/commands/tcpip.ts +9 -9
  154. package/src/daemon/auth.ts +12 -10
  155. package/src/daemon/crypto.ts +66 -55
  156. package/src/daemon/device.ts +2 -2
  157. package/src/daemon/dispatcher.ts +106 -44
  158. package/src/daemon/packet.ts +57 -38
  159. package/src/daemon/socket.ts +30 -50
  160. package/src/daemon/transport.ts +98 -50
  161. package/src/device-observer.ts +10 -0
  162. package/src/features.ts +13 -11
  163. package/src/index.ts +1 -0
  164. package/src/server/client.ts +505 -268
  165. package/src/server/transport.ts +7 -9
  166. package/src/utils/base64.ts +59 -40
  167. package/src/utils/hex.ts +16 -14
  168. package/src/utils/index.ts +1 -1
  169. package/src/utils/no-op.ts +1 -0
  170. package/src/utils/sequence-equal.ts +13 -0
  171. package/tsconfig.build.json +1 -12
  172. package/tsconfig.build.tsbuildinfo +1 -1
  173. package/CHANGELOG.json +0 -265
  174. package/esm/utils/conditional-variable.d.ts +0 -8
  175. package/esm/utils/conditional-variable.d.ts.map +0 -1
  176. package/esm/utils/conditional-variable.js +0 -34
  177. package/esm/utils/conditional-variable.js.map +0 -1
  178. package/src/utils/conditional-variable.ts +0 -45
@@ -1,10 +1,10 @@
1
- import type { Consumable, ReadableStream } from "@yume-chan/stream-extra";
1
+ import type { ReadableStream } from "@yume-chan/stream-extra";
2
2
  import {
3
3
  AbortController,
4
- ConsumableWritableStream,
5
4
  DistributionStream,
5
+ MaybeConsumable,
6
6
  } from "@yume-chan/stream-extra";
7
- import Struct, { placeholder } from "@yume-chan/struct";
7
+ import { struct, u32 } from "@yume-chan/struct";
8
8
 
9
9
  import { NOOP } from "../../utils/index.js";
10
10
 
@@ -18,20 +18,21 @@ export const ADB_SYNC_MAX_PACKET_SIZE = 64 * 1024;
18
18
  export interface AdbSyncPushV1Options {
19
19
  socket: AdbSyncSocket;
20
20
  filename: string;
21
- file: ReadableStream<Consumable<Uint8Array>>;
21
+ file: ReadableStream<MaybeConsumable<Uint8Array>>;
22
22
  type?: LinuxFileType;
23
23
  permission?: number;
24
24
  mtime?: number;
25
25
  packetSize?: number;
26
26
  }
27
27
 
28
- export const AdbSyncOkResponse = new Struct({ littleEndian: true }).uint32(
29
- "unused",
28
+ export const AdbSyncOkResponse = struct(
29
+ { unused: u32 },
30
+ { littleEndian: true },
30
31
  );
31
32
 
32
33
  async function pipeFileData(
33
34
  locked: AdbSyncSocketLocked,
34
- file: ReadableStream<Consumable<Uint8Array>>,
35
+ file: ReadableStream<MaybeConsumable<Uint8Array>>,
35
36
  packetSize: number,
36
37
  mtime: number,
37
38
  ) {
@@ -40,9 +41,9 @@ async function pipeFileData(
40
41
  const abortController = new AbortController();
41
42
  file.pipeThrough(new DistributionStream(packetSize, true))
42
43
  .pipeTo(
43
- new ConsumableWritableStream({
44
- write: async (chunk) => {
45
- await adbSyncWriteRequest(
44
+ new MaybeConsumable.WritableStream({
45
+ write(chunk) {
46
+ return adbSyncWriteRequest(
46
47
  locked,
47
48
  AdbSyncRequestId.Data,
48
49
  chunk,
@@ -86,22 +87,22 @@ export async function adbSyncPushV1({
86
87
  }
87
88
  }
88
89
 
89
- export enum AdbSyncSendV2Flags {
90
- None = 0,
91
- Brotli = 1,
90
+ export const AdbSyncSendV2Flags = {
91
+ None: 0,
92
+ Brotli: 1,
92
93
  /**
93
94
  * 2
94
95
  */
95
- Lz4 = 1 << 1,
96
+ Lz4: 1 << 1,
96
97
  /**
97
98
  * 4
98
99
  */
99
- Zstd = 1 << 2,
100
- /**
101
- * 0x80000000
102
- */
103
- DryRun = (1 << 31) >>> 0,
104
- }
100
+ Zstd: 1 << 2,
101
+ DryRun: 0x80000000,
102
+ } as const;
103
+
104
+ export type AdbSyncSendV2Flags =
105
+ (typeof AdbSyncSendV2Flags)[keyof typeof AdbSyncSendV2Flags];
105
106
 
106
107
  export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
107
108
  /**
@@ -113,10 +114,10 @@ export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
113
114
  dryRun?: boolean;
114
115
  }
115
116
 
116
- export const AdbSyncSendV2Request = new Struct({ littleEndian: true })
117
- .uint32("id", placeholder<AdbSyncRequestId>())
118
- .uint32("mode")
119
- .uint32("flags", placeholder<AdbSyncSendV2Flags>());
117
+ export const AdbSyncSendV2Request = struct(
118
+ { id: u32, mode: u32, flags: u32<AdbSyncSendV2Flags>() },
119
+ { littleEndian: true },
120
+ );
120
121
 
121
122
  export async function adbSyncPushV2({
122
123
  socket,
@@ -1,27 +1,24 @@
1
- import Struct from "@yume-chan/struct";
2
-
3
- import { encodeUtf8 } from "../../utils/index.js";
4
-
5
- export enum AdbSyncRequestId {
6
- List = "LIST",
7
- ListV2 = "LIS2",
8
- Send = "SEND",
9
- SendV2 = "SND2",
10
- Lstat = "STAT",
11
- Stat = "STA2",
12
- LstatV2 = "LST2",
13
- Data = "DATA",
14
- Done = "DONE",
15
- Receive = "RECV",
16
- }
1
+ import { encodeUtf8, struct, u32 } from "@yume-chan/struct";
2
+
3
+ import { adbSyncEncodeId } from "./response.js";
17
4
 
18
- export const AdbSyncNumberRequest = new Struct({ littleEndian: true })
19
- .string("id", { length: 4 })
20
- .uint32("arg");
5
+ export const AdbSyncRequestId = {
6
+ List: adbSyncEncodeId("LIST"),
7
+ ListV2: adbSyncEncodeId("LIS2"),
8
+ Send: adbSyncEncodeId("SEND"),
9
+ SendV2: adbSyncEncodeId("SND2"),
10
+ Lstat: adbSyncEncodeId("STAT"),
11
+ Stat: adbSyncEncodeId("STA2"),
12
+ LstatV2: adbSyncEncodeId("LST2"),
13
+ Data: adbSyncEncodeId("DATA"),
14
+ Done: adbSyncEncodeId("DONE"),
15
+ Receive: adbSyncEncodeId("RECV"),
16
+ } as const;
21
17
 
22
- export const AdbSyncDataRequest = new Struct({ littleEndian: true })
23
- .concat(AdbSyncNumberRequest)
24
- .uint8Array("data", { lengthField: "arg" });
18
+ export const AdbSyncNumberRequest = struct(
19
+ { id: u32, arg: u32 },
20
+ { littleEndian: true },
21
+ );
25
22
 
26
23
  export interface AdbSyncWritable {
27
24
  write(buffer: Uint8Array): Promise<void>;
@@ -29,26 +26,28 @@ export interface AdbSyncWritable {
29
26
 
30
27
  export async function adbSyncWriteRequest(
31
28
  writable: AdbSyncWritable,
32
- id: AdbSyncRequestId | string,
29
+ id: number | string,
33
30
  value: number | string | Uint8Array,
34
31
  ): Promise<void> {
32
+ if (typeof id === "string") {
33
+ id = adbSyncEncodeId(id);
34
+ }
35
+
35
36
  if (typeof value === "number") {
36
- const buffer = AdbSyncNumberRequest.serialize({
37
- id,
38
- arg: value,
39
- });
40
- await writable.write(buffer);
41
- } else if (typeof value === "string") {
42
- // Let `writable` buffer writes
43
- const buffer = encodeUtf8(value);
44
37
  await writable.write(
45
- AdbSyncNumberRequest.serialize({ id, arg: buffer.byteLength }),
38
+ AdbSyncNumberRequest.serialize({ id, arg: value }),
46
39
  );
47
- await writable.write(buffer);
48
- } else {
49
- await writable.write(
50
- AdbSyncNumberRequest.serialize({ id, arg: value.byteLength }),
51
- );
52
- await writable.write(value);
40
+ return;
41
+ }
42
+
43
+ if (typeof value === "string") {
44
+ value = encodeUtf8(value);
53
45
  }
46
+
47
+ // `writable` is buffered, it copies inputs to an internal buffer,
48
+ // so don't concatenate headers and data here, that will be an unnecessary copy.
49
+ await writable.write(
50
+ AdbSyncNumberRequest.serialize({ id, arg: value.length }),
51
+ );
52
+ await writable.write(value);
54
53
  }
@@ -1,60 +1,86 @@
1
- import type {
2
- AsyncExactReadable,
3
- StructLike,
4
- StructValueType,
5
- } from "@yume-chan/struct";
6
- import Struct from "@yume-chan/struct";
1
+ import { getUint32LittleEndian } from "@yume-chan/no-data-view";
2
+ import type { AsyncExactReadable, StructLike } from "@yume-chan/struct";
3
+ import { decodeUtf8, string, struct, u32 } from "@yume-chan/struct";
7
4
 
8
- import { decodeUtf8 } from "../../utils/index.js";
5
+ function encodeAsciiUnchecked(value: string): Uint8Array {
6
+ const result = new Uint8Array(value.length);
7
+ for (let i = 0; i < value.length; i += 1) {
8
+ result[i] = value.charCodeAt(i);
9
+ }
10
+ return result;
11
+ }
9
12
 
10
- export enum AdbSyncResponseId {
11
- Entry = "DENT",
12
- Entry2 = "DNT2",
13
- Lstat = "STAT",
14
- Stat = "STA2",
15
- Lstat2 = "LST2",
16
- Done = "DONE",
17
- Data = "DATA",
18
- Ok = "OKAY",
19
- Fail = "FAIL",
13
+ /**
14
+ * Encode ID to numbers for faster comparison
15
+ * @param value A 4-character string
16
+ * @returns A 32-bit integer by encoding the string as little-endian
17
+ *
18
+ * #__NO_SIDE_EFFECTS__
19
+ */
20
+ export function adbSyncEncodeId(value: string): number {
21
+ const buffer = encodeAsciiUnchecked(value);
22
+ return getUint32LittleEndian(buffer, 0);
20
23
  }
21
24
 
25
+ export const AdbSyncResponseId = {
26
+ Entry: adbSyncEncodeId("DENT"),
27
+ Entry2: adbSyncEncodeId("DNT2"),
28
+ Lstat: adbSyncEncodeId("STAT"),
29
+ Stat: adbSyncEncodeId("STA2"),
30
+ Lstat2: adbSyncEncodeId("LST2"),
31
+ Done: adbSyncEncodeId("DONE"),
32
+ Data: adbSyncEncodeId("DATA"),
33
+ Ok: adbSyncEncodeId("OKAY"),
34
+ Fail: adbSyncEncodeId("FAIL"),
35
+ };
36
+
22
37
  export class AdbSyncError extends Error {}
23
38
 
24
- export const AdbSyncFailResponse = new Struct({ littleEndian: true })
25
- .uint32("messageLength")
26
- .string("message", { lengthField: "messageLength" })
27
- .postDeserialize((object) => {
28
- throw new AdbSyncError(object.message);
29
- });
39
+ export const AdbSyncFailResponse = struct(
40
+ { message: string(u32) },
41
+ {
42
+ littleEndian: true,
43
+ postDeserialize(value) {
44
+ throw new AdbSyncError(value.message);
45
+ },
46
+ },
47
+ );
30
48
 
31
49
  export async function adbSyncReadResponse<T>(
32
50
  stream: AsyncExactReadable,
33
- id: AdbSyncResponseId,
51
+ id: number | string,
34
52
  type: StructLike<T>,
35
53
  ): Promise<T> {
36
- const actualId = decodeUtf8(await stream.readExactly(4));
37
- switch (actualId) {
54
+ if (typeof id === "string") {
55
+ id = adbSyncEncodeId(id);
56
+ }
57
+
58
+ const buffer = await stream.readExactly(4);
59
+ switch (getUint32LittleEndian(buffer, 0)) {
38
60
  case AdbSyncResponseId.Fail:
39
61
  await AdbSyncFailResponse.deserialize(stream);
40
62
  throw new Error("Unreachable");
41
63
  case id:
42
64
  return await type.deserialize(stream);
43
65
  default:
44
- throw new Error(`Expected '${id}', but got '${actualId}'`);
66
+ throw new Error(
67
+ `Expected '${id}', but got '${decodeUtf8(buffer)}'`,
68
+ );
45
69
  }
46
70
  }
47
71
 
48
- export async function* adbSyncReadResponses<
49
- T extends Struct<object, PropertyKey, object, unknown>,
50
- >(
72
+ export async function* adbSyncReadResponses<T>(
51
73
  stream: AsyncExactReadable,
52
- id: AdbSyncResponseId,
53
- type: T,
54
- ): AsyncGenerator<StructValueType<T>, void, void> {
74
+ id: number | string,
75
+ type: StructLike<T>,
76
+ ): AsyncGenerator<T, void, void> {
77
+ if (typeof id === "string") {
78
+ id = adbSyncEncodeId(id);
79
+ }
80
+
55
81
  while (true) {
56
- const actualId = decodeUtf8(await stream.readExactly(4));
57
- switch (actualId) {
82
+ const buffer = await stream.readExactly(4);
83
+ switch (getUint32LittleEndian(buffer, 0)) {
58
84
  case AdbSyncResponseId.Fail:
59
85
  await AdbSyncFailResponse.deserialize(stream);
60
86
  throw new Error("Unreachable");
@@ -66,11 +92,11 @@ export async function* adbSyncReadResponses<
66
92
  await stream.readExactly(type.size);
67
93
  return;
68
94
  case id:
69
- yield (await type.deserialize(stream)) as StructValueType<T>;
95
+ yield await type.deserialize(stream);
70
96
  break;
71
97
  default:
72
98
  throw new Error(
73
- `Expected '${id}' or '${AdbSyncResponseId.Done}', but got '${actualId}'`,
99
+ `Expected '${id}' or '${AdbSyncResponseId.Done}', but got '${decodeUtf8(buffer)}'`,
74
100
  );
75
101
  }
76
102
  }
@@ -1,11 +1,11 @@
1
1
  import type {
2
- Consumable,
2
+ MaybeConsumable,
3
3
  WritableStreamDefaultWriter,
4
4
  } from "@yume-chan/stream-extra";
5
5
  import {
6
6
  BufferCombiner,
7
7
  BufferedReadableStream,
8
- ConsumableWritableStream,
8
+ Consumable,
9
9
  } from "@yume-chan/stream-extra";
10
10
  import type { AsyncExactReadable } from "@yume-chan/struct";
11
11
 
@@ -13,7 +13,7 @@ import type { AdbSocket } from "../../adb.js";
13
13
  import { AutoResetEvent } from "../../utils/index.js";
14
14
 
15
15
  export class AdbSyncSocketLocked implements AsyncExactReadable {
16
- readonly #writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
16
+ readonly #writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>;
17
17
  readonly #readable: BufferedReadableStream;
18
18
  readonly #socketLock: AutoResetEvent;
19
19
  readonly #writeLock = new AutoResetEvent();
@@ -24,7 +24,7 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
24
24
  }
25
25
 
26
26
  constructor(
27
- writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>,
27
+ writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>,
28
28
  readable: BufferedReadableStream,
29
29
  bufferSize: number,
30
30
  lock: AutoResetEvent,
@@ -35,8 +35,9 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
35
35
  this.#combiner = new BufferCombiner(bufferSize);
36
36
  }
37
37
 
38
- async #writeInnerStream(buffer: Uint8Array) {
39
- await ConsumableWritableStream.write(this.#writer, buffer);
38
+ #write(buffer: Uint8Array) {
39
+ // `#combiner` will reuse the buffer, so we need to use the Consumable pattern
40
+ return Consumable.WritableStream.write(this.#writer, buffer);
40
41
  }
41
42
 
42
43
  async flush() {
@@ -44,7 +45,7 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
44
45
  await this.#writeLock.wait();
45
46
  const buffer = this.#combiner.flush();
46
47
  if (buffer) {
47
- await this.#writeInnerStream(buffer);
48
+ await this.#write(buffer);
48
49
  }
49
50
  } finally {
50
51
  this.#writeLock.notifyOne();
@@ -55,7 +56,7 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
55
56
  try {
56
57
  await this.#writeLock.wait();
57
58
  for (const buffer of this.#combiner.push(data)) {
58
- await this.#writeInnerStream(buffer);
59
+ await this.#write(buffer);
59
60
  }
60
61
  } finally {
61
62
  this.#writeLock.notifyOne();
@@ -63,11 +64,15 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
63
64
  }
64
65
 
65
66
  async readExactly(length: number) {
67
+ // The request may still be in the internal buffer.
68
+ // Call `flush` to send it before starting reading
66
69
  await this.flush();
67
70
  return await this.#readable.readExactly(length);
68
71
  }
69
72
 
70
73
  release(): void {
74
+ // In theory, the writer shouldn't leave anything in the buffer,
75
+ // but to be safe, call `flush` to throw away any remaining data.
71
76
  this.#combiner.flush();
72
77
  this.#socketLock.notifyOne();
73
78
  }
@@ -1,15 +1,18 @@
1
- import Struct, { placeholder } from "@yume-chan/struct";
1
+ import type { StructValue } from "@yume-chan/struct";
2
+ import { struct, u32, u64 } from "@yume-chan/struct";
2
3
 
3
4
  import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
4
5
  import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js";
5
6
  import type { AdbSyncSocket } from "./socket.js";
6
7
 
7
8
  // https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36
8
- export enum LinuxFileType {
9
- Directory = 0o04,
10
- File = 0o10,
11
- Link = 0o12,
12
- }
9
+ export const LinuxFileType = {
10
+ Directory: 0o04,
11
+ File: 0o10,
12
+ Link: 0o12,
13
+ } as const;
14
+
15
+ export type LinuxFileType = (typeof LinuxFileType)[keyof typeof LinuxFileType];
13
16
 
14
17
  export interface AdbSyncStat {
15
18
  mode: number;
@@ -24,81 +27,98 @@ export interface AdbSyncStat {
24
27
  ctime?: bigint;
25
28
  }
26
29
 
27
- export const AdbSyncLstatResponse = new Struct({ littleEndian: true })
28
- .int32("mode")
29
- .int32("size")
30
- .int32("mtime")
31
- .extra({
32
- id: AdbSyncResponseId.Lstat as const,
33
- get type() {
34
- return (this.mode >> 12) as LinuxFileType;
30
+ export const AdbSyncLstatResponse = struct(
31
+ { mode: u32, size: u32, mtime: u32 },
32
+ {
33
+ littleEndian: true,
34
+ extra: {
35
+ get type(): LinuxFileType {
36
+ return (this.mode >> 12) as LinuxFileType;
37
+ },
38
+ get permission(): number {
39
+ return this.mode & 0b00001111_11111111;
40
+ },
35
41
  },
36
- get permission() {
37
- return this.mode & 0b00001111_11111111;
42
+ postDeserialize(value) {
43
+ if (value.mode === 0 && value.size === 0 && value.mtime === 0) {
44
+ throw new Error("lstat error");
45
+ }
46
+ return value;
38
47
  },
39
- })
40
- .postDeserialize((object) => {
41
- if (object.mode === 0 && object.size === 0 && object.mtime === 0) {
42
- throw new Error("lstat error");
43
- }
44
- });
48
+ },
49
+ );
45
50
 
46
- export type AdbSyncLstatResponse =
47
- (typeof AdbSyncLstatResponse)["TDeserializeResult"];
51
+ export type AdbSyncLstatResponse = StructValue<typeof AdbSyncLstatResponse>;
48
52
 
49
- export enum AdbSyncStatErrorCode {
50
- SUCCESS = 0,
51
- EACCES = 13,
52
- EEXIST = 17,
53
- EFAULT = 14,
54
- EFBIG = 27,
55
- EINTR = 4,
56
- EINVAL = 22,
57
- EIO = 5,
58
- EISDIR = 21,
59
- ELOOP = 40,
60
- EMFILE = 24,
61
- ENAMETOOLONG = 36,
62
- ENFILE = 23,
63
- ENOENT = 2,
64
- ENOMEM = 12,
65
- ENOSPC = 28,
66
- ENOTDIR = 20,
67
- EOVERFLOW = 75,
68
- EPERM = 1,
69
- EROFS = 30,
70
- ETXTBSY = 26,
71
- }
53
+ export const AdbSyncStatErrorCode = {
54
+ SUCCESS: 0,
55
+ EACCES: 13,
56
+ EEXIST: 17,
57
+ EFAULT: 14,
58
+ EFBIG: 27,
59
+ EINTR: 4,
60
+ EINVAL: 22,
61
+ EIO: 5,
62
+ EISDIR: 21,
63
+ ELOOP: 40,
64
+ EMFILE: 24,
65
+ ENAMETOOLONG: 36,
66
+ ENFILE: 23,
67
+ ENOENT: 2,
68
+ ENOMEM: 12,
69
+ ENOSPC: 28,
70
+ ENOTDIR: 20,
71
+ EOVERFLOW: 75,
72
+ EPERM: 1,
73
+ EROFS: 30,
74
+ ETXTBSY: 26,
75
+ } as const;
72
76
 
73
- export const AdbSyncStatResponse = new Struct({ littleEndian: true })
74
- .uint32("error", placeholder<AdbSyncStatErrorCode>())
75
- .uint64("dev")
76
- .uint64("ino")
77
- .uint32("mode")
78
- .uint32("nlink")
79
- .uint32("uid")
80
- .uint32("gid")
81
- .uint64("size")
82
- .uint64("atime")
83
- .uint64("mtime")
84
- .uint64("ctime")
85
- .extra({
86
- id: AdbSyncResponseId.Stat as const,
87
- get type() {
88
- return (this.mode >> 12) as LinuxFileType;
77
+ export type AdbSyncStatErrorCode =
78
+ (typeof AdbSyncStatErrorCode)[keyof typeof AdbSyncStatErrorCode];
79
+
80
+ const AdbSyncStatErrorName = /* #__PURE__ */ (() =>
81
+ Object.fromEntries(
82
+ Object.entries(AdbSyncStatErrorCode).map(([key, value]) => [
83
+ value,
84
+ key,
85
+ ]),
86
+ ))();
87
+
88
+ export const AdbSyncStatResponse = struct(
89
+ {
90
+ error: u32<AdbSyncStatErrorCode>(),
91
+ dev: u64,
92
+ ino: u64,
93
+ mode: u32,
94
+ nlink: u32,
95
+ uid: u32,
96
+ gid: u32,
97
+ size: u64,
98
+ atime: u64,
99
+ mtime: u64,
100
+ ctime: u64,
101
+ },
102
+ {
103
+ littleEndian: true,
104
+ extra: {
105
+ get type(): LinuxFileType {
106
+ return (this.mode >> 12) as LinuxFileType;
107
+ },
108
+ get permission(): number {
109
+ return this.mode & 0b00001111_11111111;
110
+ },
89
111
  },
90
- get permission() {
91
- return this.mode & 0b00001111_11111111;
112
+ postDeserialize(value) {
113
+ if (value.error) {
114
+ throw new Error(AdbSyncStatErrorName[value.error]);
115
+ }
116
+ return value;
92
117
  },
93
- })
94
- .postDeserialize((object) => {
95
- if (object.error) {
96
- throw new Error(AdbSyncStatErrorCode[object.error]);
97
- }
98
- });
118
+ },
119
+ );
99
120
 
100
- export type AdbSyncStatResponse =
101
- (typeof AdbSyncStatResponse)["TDeserializeResult"];
121
+ export type AdbSyncStatResponse = StructValue<typeof AdbSyncStatResponse>;
102
122
 
103
123
  export async function adbSyncLstat(
104
124
  socket: AdbSyncSocket,
@@ -1,5 +1,4 @@
1
- import { AutoDisposable } from "@yume-chan/event";
2
- import type { Consumable, ReadableStream } from "@yume-chan/stream-extra";
1
+ import type { MaybeConsumable, ReadableStream } from "@yume-chan/stream-extra";
3
2
 
4
3
  import type { Adb, AdbSocket } from "../../adb.js";
5
4
  import { AdbFeature } from "../../features.js";
@@ -9,6 +8,7 @@ import type { AdbSyncEntry } from "./list.js";
9
8
  import { adbSyncOpenDir } from "./list.js";
10
9
  import { adbSyncPull } from "./pull.js";
11
10
  import { adbSyncPush } from "./push.js";
11
+ import type { AdbSyncSocketLocked } from "./socket.js";
12
12
  import { AdbSyncSocket } from "./socket.js";
13
13
  import type { AdbSyncStat, LinuxFileType } from "./stat.js";
14
14
  import { adbSyncLstat, adbSyncStat } from "./stat.js";
@@ -31,14 +31,14 @@ export function dirname(path: string): string {
31
31
 
32
32
  export interface AdbSyncWriteOptions {
33
33
  filename: string;
34
- file: ReadableStream<Consumable<Uint8Array>>;
34
+ file: ReadableStream<MaybeConsumable<Uint8Array>>;
35
35
  type?: LinuxFileType;
36
36
  permission?: number;
37
37
  mtime?: number;
38
38
  dryRun?: boolean;
39
39
  }
40
40
 
41
- export class AdbSync extends AutoDisposable {
41
+ export class AdbSync {
42
42
  protected _adb: Adb;
43
43
  protected _socket: AdbSyncSocket;
44
44
 
@@ -69,8 +69,6 @@ export class AdbSync extends AutoDisposable {
69
69
  }
70
70
 
71
71
  constructor(adb: Adb, socket: AdbSocket) {
72
- super();
73
-
74
72
  this._adb = adb;
75
73
  this._socket = new AdbSyncSocket(socket, adb.maxPayloadSize);
76
74
 
@@ -116,7 +114,7 @@ export class AdbSync extends AutoDisposable {
116
114
  try {
117
115
  await this.lstat(path + "/");
118
116
  return true;
119
- } catch (e) {
117
+ } catch {
120
118
  return false;
121
119
  }
122
120
  }
@@ -150,7 +148,7 @@ export class AdbSync extends AutoDisposable {
150
148
  */
151
149
  async write(options: AdbSyncWriteOptions): Promise<void> {
152
150
  if (this.needPushMkdirWorkaround) {
153
- // It may fail if the path is already existed.
151
+ // It may fail if `filename` already exists.
154
152
  // Ignore the result.
155
153
  // TODO: sync: test push mkdir workaround (need an Android 8 device)
156
154
  await this._adb.subprocess.spawnAndWait([
@@ -167,8 +165,11 @@ export class AdbSync extends AutoDisposable {
167
165
  });
168
166
  }
169
167
 
170
- override async dispose() {
171
- super.dispose();
172
- await this._socket.close();
168
+ lockSocket(): Promise<AdbSyncSocketLocked> {
169
+ return this._socket.lock();
170
+ }
171
+
172
+ dispose() {
173
+ return this._socket.close();
173
174
  }
174
175
  }