@yume-chan/adb 0.0.13 → 0.0.16

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 (116) hide show
  1. package/CHANGELOG.json +57 -0
  2. package/CHANGELOG.md +29 -1
  3. package/LICENSE +21 -21
  4. package/README.md +247 -247
  5. package/esm/adb.d.ts +12 -4
  6. package/esm/adb.d.ts.map +1 -1
  7. package/esm/adb.js +29 -19
  8. package/esm/adb.js.map +1 -1
  9. package/esm/commands/reverse.d.ts +9 -12
  10. package/esm/commands/reverse.d.ts.map +1 -1
  11. package/esm/commands/reverse.js +44 -36
  12. package/esm/commands/reverse.js.map +1 -1
  13. package/esm/commands/subprocess/protocols/none.d.ts +2 -1
  14. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  15. package/esm/commands/subprocess/protocols/none.js +10 -9
  16. package/esm/commands/subprocess/protocols/none.js.map +1 -1
  17. package/esm/commands/sync/pull.d.ts.map +1 -1
  18. package/esm/commands/sync/pull.js +6 -3
  19. package/esm/commands/sync/pull.js.map +1 -1
  20. package/esm/commands/sync/stat.d.ts.map +1 -1
  21. package/esm/commands/sync/stat.js +2 -2
  22. package/esm/commands/sync/stat.js.map +1 -1
  23. package/esm/commands/sync/sync.js +1 -1
  24. package/esm/commands/sync/sync.js.map +1 -1
  25. package/esm/socket/dispatcher.d.ts +25 -14
  26. package/esm/socket/dispatcher.d.ts.map +1 -1
  27. package/esm/socket/dispatcher.js +94 -63
  28. package/esm/socket/dispatcher.js.map +1 -1
  29. package/esm/socket/socket.d.ts +18 -4
  30. package/esm/socket/socket.d.ts.map +1 -1
  31. package/esm/socket/socket.js +36 -21
  32. package/esm/socket/socket.js.map +1 -1
  33. package/esm/stream/buffered.d.ts +7 -3
  34. package/esm/stream/buffered.d.ts.map +1 -1
  35. package/esm/stream/buffered.js +69 -49
  36. package/esm/stream/buffered.js.map +1 -1
  37. package/esm/stream/detect.js +1 -0
  38. package/esm/stream/detect.js.map +1 -1
  39. package/esm/stream/detect.polyfill.d.ts +1 -1
  40. package/esm/stream/detect.polyfill.d.ts.map +1 -1
  41. package/esm/stream/detect.polyfill.js +0 -5
  42. package/esm/stream/detect.polyfill.js.map +1 -1
  43. package/esm/stream/transform.d.ts +39 -13
  44. package/esm/stream/transform.d.ts.map +1 -1
  45. package/esm/stream/transform.js +67 -90
  46. package/esm/stream/transform.js.map +1 -1
  47. package/esm/utils/base64.js +5 -5
  48. package/esm/utils/base64.js.map +1 -1
  49. package/package.json +10 -10
  50. package/src/adb.ts +280 -270
  51. package/src/auth.ts +173 -173
  52. package/src/backend.ts +11 -11
  53. package/src/commands/base.ts +11 -11
  54. package/src/commands/framebuffer.ts +73 -73
  55. package/src/commands/index.ts +8 -8
  56. package/src/commands/install.ts +30 -30
  57. package/src/commands/power.ts +54 -54
  58. package/src/commands/reverse.ts +140 -136
  59. package/src/commands/subprocess/index.ts +139 -139
  60. package/src/commands/subprocess/protocols/none.ts +70 -68
  61. package/src/commands/subprocess/protocols/shell.ts +190 -190
  62. package/src/commands/subprocess/protocols/types.ts +59 -59
  63. package/src/commands/subprocess/utils.ts +20 -20
  64. package/src/commands/sync/index.ts +7 -7
  65. package/src/commands/sync/list.ts +86 -86
  66. package/src/commands/sync/pull.ts +46 -43
  67. package/src/commands/sync/push.ts +41 -41
  68. package/src/commands/sync/request.ts +50 -50
  69. package/src/commands/sync/response.ts +64 -64
  70. package/src/commands/sync/stat.ts +151 -150
  71. package/src/commands/sync/sync.ts +181 -181
  72. package/src/commands/tcpip.ts +21 -21
  73. package/src/crypto.ts +296 -296
  74. package/src/features.ts +9 -9
  75. package/src/index.ts +11 -11
  76. package/src/packet.ts +80 -80
  77. package/src/socket/dispatcher.ts +305 -270
  78. package/src/socket/index.ts +2 -2
  79. package/src/socket/socket.ts +155 -136
  80. package/src/stream/buffered.ts +168 -147
  81. package/src/stream/detect.native.ts +362 -362
  82. package/src/stream/detect.polyfill.ts +22 -28
  83. package/src/stream/detect.ts +7 -5
  84. package/src/stream/index.ts +3 -3
  85. package/src/stream/transform.ts +474 -483
  86. package/src/utils/auto-reset-event.ts +41 -41
  87. package/src/utils/base64.ts +306 -306
  88. package/src/utils/index.ts +3 -3
  89. package/tsconfig.build.json +3 -3
  90. package/tsconfig.build.tsbuildinfo +1 -1
  91. package/tsconfig.test.json +9 -10
  92. package/web-streams-polyfill-4.0.0-beta.2.tgz +0 -0
  93. package/esm/commands/subprocess/legacy.d.ts +0 -35
  94. package/esm/commands/subprocess/legacy.d.ts.map +0 -1
  95. package/esm/commands/subprocess/legacy.js +0 -53
  96. package/esm/commands/subprocess/legacy.js.map +0 -1
  97. package/esm/commands/subprocess/protocol.d.ts +0 -39
  98. package/esm/commands/subprocess/protocol.d.ts.map +0 -1
  99. package/esm/commands/subprocess/protocol.js +0 -157
  100. package/esm/commands/subprocess/protocol.js.map +0 -1
  101. package/esm/commands/subprocess/types.d.ts +0 -50
  102. package/esm/commands/subprocess/types.d.ts.map +0 -1
  103. package/esm/commands/subprocess/types.js +0 -2
  104. package/esm/commands/subprocess/types.js.map +0 -1
  105. package/esm/socket/controller.d.ts +0 -37
  106. package/esm/socket/controller.d.ts.map +0 -1
  107. package/esm/socket/controller.js +0 -66
  108. package/esm/socket/controller.js.map +0 -1
  109. package/esm/stream/detect.bak.d.ts +0 -240
  110. package/esm/stream/detect.bak.d.ts.map +0 -1
  111. package/esm/stream/detect.bak.js +0 -60
  112. package/esm/stream/detect.bak.js.map +0 -1
  113. package/esm/utils/encoding.d.ts +0 -3
  114. package/esm/utils/encoding.d.ts.map +0 -1
  115. package/esm/utils/encoding.js +0 -11
  116. package/esm/utils/encoding.js.map +0 -1
@@ -1,150 +1,151 @@
1
- import Struct, { placeholder } from '@yume-chan/struct';
2
- import type { AdbBufferedStream, WritableStreamDefaultWriter } from '../../stream/index.js';
3
- import { AdbSyncRequestId, adbSyncWriteRequest } from './request.js';
4
- import { adbSyncReadResponse, AdbSyncResponseId } from './response.js';
5
-
6
- // https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36
7
- export enum LinuxFileType {
8
- Directory = 0o04,
9
- File = 0o10,
10
- Link = 0o12,
11
- }
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
-
26
- export const AdbSyncLstatResponse =
27
- new Struct({ littleEndian: true })
28
- .int32('mode')
29
- .int32('size')
30
- .int32('mtime')
31
- .extra({
32
- id: AdbSyncResponseId.Lstat as const,
33
- get type() { return this.mode >> 12 as LinuxFileType; },
34
- get permission() { return this.mode & 0b00001111_11111111; },
35
- })
36
- .postDeserialize((object) => {
37
- if (object.mode === 0 &&
38
- object.size === 0 &&
39
- object.mtime === 0
40
- ) {
41
- throw new Error('lstat failed');
42
- }
43
- });
44
-
45
- export type AdbSyncLstatResponse = typeof AdbSyncLstatResponse['TDeserializeResult'];
46
-
47
- export enum AdbSyncStatErrorCode {
48
- SUCCESS = 0,
49
- EACCES = 13,
50
- EEXIST = 17,
51
- EFAULT = 14,
52
- EFBIG = 27,
53
- EINTR = 4,
54
- EINVAL = 22,
55
- EIO = 5,
56
- EISDIR = 21,
57
- ELOOP = 40,
58
- EMFILE = 24,
59
- ENAMETOOLONG = 36,
60
- ENFILE = 23,
61
- ENOENT = 2,
62
- ENOMEM = 12,
63
- ENOSPC = 28,
64
- ENOTDIR = 20,
65
- EOVERFLOW = 75,
66
- EPERM = 1,
67
- EROFS = 30,
68
- ETXTBSY = 26,
69
- }
70
-
71
- export const AdbSyncStatResponse =
72
- new Struct({ littleEndian: true })
73
- .uint32('error', placeholder<AdbSyncStatErrorCode>())
74
- .uint64('dev')
75
- .uint64('ino')
76
- .uint32('mode')
77
- .uint32('nlink')
78
- .uint32('uid')
79
- .uint32('gid')
80
- .uint64('size')
81
- .uint64('atime')
82
- .uint64('mtime')
83
- .uint64('ctime')
84
- .extra({
85
- id: AdbSyncResponseId.Stat as const,
86
- get type() { return this.mode >> 12 as LinuxFileType; },
87
- get permission() { return this.mode & 0b00001111_11111111; },
88
- })
89
- .postDeserialize((object) => {
90
- if (object.error) {
91
- throw new Error(AdbSyncStatErrorCode[object.error]);
92
- }
93
- });
94
-
95
- export type AdbSyncStatResponse = typeof AdbSyncStatResponse['TDeserializeResult'];
96
-
97
- const STAT_RESPONSE_TYPES = {
98
- [AdbSyncResponseId.Stat]: AdbSyncStatResponse,
99
- };
100
-
101
- const LSTAT_RESPONSE_TYPES = {
102
- [AdbSyncResponseId.Lstat]: AdbSyncLstatResponse,
103
- };
104
-
105
- const LSTAT_V2_RESPONSE_TYPES = {
106
- [AdbSyncResponseId.Lstat2]: AdbSyncStatResponse,
107
- };
108
-
109
- export async function adbSyncLstat(
110
- stream: AdbBufferedStream,
111
- writer: WritableStreamDefaultWriter<Uint8Array>,
112
- path: string,
113
- v2: boolean,
114
- ): Promise<AdbSyncStat> {
115
- let requestId: AdbSyncRequestId.Lstat | AdbSyncRequestId.Lstat2;
116
- let responseType: typeof LSTAT_RESPONSE_TYPES | typeof LSTAT_V2_RESPONSE_TYPES;
117
-
118
- if (v2) {
119
- requestId = AdbSyncRequestId.Lstat2;
120
- responseType = LSTAT_V2_RESPONSE_TYPES;
121
- } else {
122
- requestId = AdbSyncRequestId.Lstat;
123
- responseType = LSTAT_RESPONSE_TYPES;
124
- }
125
-
126
- await adbSyncWriteRequest(writer, requestId, path);
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
- }
141
- }
142
-
143
- export async function adbSyncStat(
144
- stream: AdbBufferedStream,
145
- writer: WritableStreamDefaultWriter<Uint8Array>,
146
- path: string,
147
- ): Promise<AdbSyncStatResponse> {
148
- await adbSyncWriteRequest(writer, AdbSyncRequestId.Stat, path);
149
- return await adbSyncReadResponse(stream, STAT_RESPONSE_TYPES);
150
- }
1
+ import Struct, { placeholder } from '@yume-chan/struct';
2
+ import type { AdbBufferedStream, WritableStreamDefaultWriter } from '../../stream/index.js';
3
+ import { AdbSyncRequestId, adbSyncWriteRequest } from './request.js';
4
+ import { adbSyncReadResponse, AdbSyncResponseId } from './response.js';
5
+
6
+ // https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36
7
+ export enum LinuxFileType {
8
+ Directory = 0o04,
9
+ File = 0o10,
10
+ Link = 0o12,
11
+ }
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
+
26
+ export const AdbSyncLstatResponse =
27
+ new Struct({ littleEndian: true })
28
+ .int32('mode')
29
+ .int32('size')
30
+ .int32('mtime')
31
+ .extra({
32
+ id: AdbSyncResponseId.Lstat as const,
33
+ get type() { return this.mode >> 12 as LinuxFileType; },
34
+ get permission() { return this.mode & 0b00001111_11111111; },
35
+ })
36
+ .postDeserialize((object) => {
37
+ if (
38
+ object.mode === 0 &&
39
+ object.size === 0 &&
40
+ object.mtime === 0
41
+ ) {
42
+ throw new Error('lstat failed');
43
+ }
44
+ });
45
+
46
+ export type AdbSyncLstatResponse = typeof AdbSyncLstatResponse['TDeserializeResult'];
47
+
48
+ export enum AdbSyncStatErrorCode {
49
+ SUCCESS = 0,
50
+ EACCES = 13,
51
+ EEXIST = 17,
52
+ EFAULT = 14,
53
+ EFBIG = 27,
54
+ EINTR = 4,
55
+ EINVAL = 22,
56
+ EIO = 5,
57
+ EISDIR = 21,
58
+ ELOOP = 40,
59
+ EMFILE = 24,
60
+ ENAMETOOLONG = 36,
61
+ ENFILE = 23,
62
+ ENOENT = 2,
63
+ ENOMEM = 12,
64
+ ENOSPC = 28,
65
+ ENOTDIR = 20,
66
+ EOVERFLOW = 75,
67
+ EPERM = 1,
68
+ EROFS = 30,
69
+ ETXTBSY = 26,
70
+ }
71
+
72
+ export const AdbSyncStatResponse =
73
+ 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() { return this.mode >> 12 as LinuxFileType; },
88
+ get permission() { return this.mode & 0b00001111_11111111; },
89
+ })
90
+ .postDeserialize((object) => {
91
+ if (object.error) {
92
+ throw new Error(AdbSyncStatErrorCode[object.error]);
93
+ }
94
+ });
95
+
96
+ export type AdbSyncStatResponse = typeof AdbSyncStatResponse['TDeserializeResult'];
97
+
98
+ const STAT_RESPONSE_TYPES = {
99
+ [AdbSyncResponseId.Stat]: AdbSyncStatResponse,
100
+ };
101
+
102
+ const LSTAT_RESPONSE_TYPES = {
103
+ [AdbSyncResponseId.Lstat]: AdbSyncLstatResponse,
104
+ };
105
+
106
+ const LSTAT_V2_RESPONSE_TYPES = {
107
+ [AdbSyncResponseId.Lstat2]: AdbSyncStatResponse,
108
+ };
109
+
110
+ export async function adbSyncLstat(
111
+ stream: AdbBufferedStream,
112
+ writer: WritableStreamDefaultWriter<Uint8Array>,
113
+ path: string,
114
+ v2: boolean,
115
+ ): Promise<AdbSyncStat> {
116
+ let requestId: AdbSyncRequestId.Lstat | AdbSyncRequestId.Lstat2;
117
+ let responseType: typeof LSTAT_RESPONSE_TYPES | typeof LSTAT_V2_RESPONSE_TYPES;
118
+
119
+ if (v2) {
120
+ requestId = AdbSyncRequestId.Lstat2;
121
+ responseType = LSTAT_V2_RESPONSE_TYPES;
122
+ } else {
123
+ requestId = AdbSyncRequestId.Lstat;
124
+ responseType = LSTAT_RESPONSE_TYPES;
125
+ }
126
+
127
+ await adbSyncWriteRequest(writer, requestId, path);
128
+ const response = await adbSyncReadResponse(stream, responseType);
129
+
130
+ switch (response.id) {
131
+ case AdbSyncResponseId.Lstat:
132
+ return {
133
+ mode: response.mode,
134
+ size: BigInt(response.size),
135
+ mtime: BigInt(response.mtime),
136
+ get type() { return response.type; },
137
+ get permission() { return response.permission; },
138
+ };
139
+ default:
140
+ return response;
141
+ }
142
+ }
143
+
144
+ export async function adbSyncStat(
145
+ stream: AdbBufferedStream,
146
+ writer: WritableStreamDefaultWriter<Uint8Array>,
147
+ path: string,
148
+ ): Promise<AdbSyncStatResponse> {
149
+ await adbSyncWriteRequest(writer, AdbSyncRequestId.Stat, path);
150
+ return await adbSyncReadResponse(stream, STAT_RESPONSE_TYPES);
151
+ }
@@ -1,181 +1,181 @@
1
- import { AutoDisposable } from '@yume-chan/event';
2
- import type { Adb } from '../../adb.js';
3
- import { AdbFeatures } from '../../features.js';
4
- import type { AdbSocket } from '../../socket/index.js';
5
- import { AdbBufferedStream, ReadableStream, WrapReadableStream, WrapWritableStream, WritableStream, WritableStreamDefaultWriter } from '../../stream/index.js';
6
- import { AutoResetEvent } from '../../utils/index.js';
7
- import { escapeArg } from "../index.js";
8
- import { adbSyncOpenDir, type AdbSyncEntry } from './list.js';
9
- import { adbSyncPull } from './pull.js';
10
- import { adbSyncPush } from './push.js';
11
- import { adbSyncLstat, adbSyncStat } from './stat.js';
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
-
29
- export class AdbSync extends AutoDisposable {
30
- protected adb: Adb;
31
-
32
- protected stream: AdbBufferedStream;
33
-
34
- protected writer: WritableStreamDefaultWriter<Uint8Array>;
35
-
36
- protected sendLock = this.addDisposable(new AutoResetEvent());
37
-
38
- public get supportsStat(): boolean {
39
- return this.adb.features!.includes(AdbFeatures.StatV2);
40
- }
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
-
55
- public constructor(adb: Adb, socket: AdbSocket) {
56
- super();
57
-
58
- this.adb = adb;
59
- this.stream = new AdbBufferedStream(socket);
60
- this.writer = socket.writable.getWriter();
61
- }
62
-
63
- public async lstat(path: string) {
64
- await this.sendLock.wait();
65
-
66
- try {
67
- return adbSyncLstat(this.stream, this.writer, path, this.supportsStat);
68
- } finally {
69
- this.sendLock.notify();
70
- }
71
- }
72
-
73
- public async stat(path: string) {
74
- if (!this.supportsStat) {
75
- throw new Error('Not supported');
76
- }
77
-
78
- await this.sendLock.wait();
79
-
80
- try {
81
- return adbSyncStat(this.stream, this.writer, path);
82
- } finally {
83
- this.sendLock.notify();
84
- }
85
- }
86
-
87
- public async isDirectory(path: string): Promise<boolean> {
88
- try {
89
- await this.lstat(path + '/');
90
- return true;
91
- } catch (e) {
92
- return false;
93
- }
94
- }
95
-
96
- public async *opendir(
97
- path: string
98
- ): AsyncGenerator<AdbSyncEntry, void, void> {
99
- await this.sendLock.wait();
100
-
101
- try {
102
- yield* adbSyncOpenDir(this.stream, this.writer, path, this.supportsList2);
103
- } finally {
104
- this.sendLock.notify();
105
- }
106
- }
107
-
108
- public async readdir(path: string) {
109
- const results: AdbSyncEntry[] = [];
110
- for await (const entry of this.opendir(path)) {
111
- results.push(entry);
112
- }
113
- return results;
114
- }
115
-
116
- /**
117
- * Read the content of a file on device.
118
- *
119
- * @param filename The full path of the file on device to read.
120
- * @returns A `ReadableStream` that reads from the file.
121
- */
122
- public read(filename: string): ReadableStream<Uint8Array> {
123
- return new WrapReadableStream({
124
- start: async () => {
125
- await this.sendLock.wait();
126
- return adbSyncPull(this.stream, this.writer, filename);
127
- },
128
- close: async () => {
129
- this.sendLock.notify();
130
- },
131
- });
132
- }
133
-
134
- /**
135
- * Write (or overwrite) a file on device.
136
- *
137
- * @param filename The full path of the file on device to write.
138
- * @param mode The unix permissions of the file.
139
- * @param mtime The modified time of the file.
140
- * @returns A `WritableStream` that writes to the file.
141
- */
142
- public write(
143
- filename: string,
144
- mode?: number,
145
- mtime?: number,
146
- ): WritableStream<Uint8Array> {
147
- return new WrapWritableStream({
148
- start: async () => {
149
- await this.sendLock.wait();
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
- );
169
- },
170
- close: async () => {
171
- this.sendLock.notify();
172
- }
173
- });
174
- }
175
-
176
- public override async dispose() {
177
- super.dispose();
178
- this.stream.close();
179
- await this.writer.close();
180
- }
181
- }
1
+ import { AutoDisposable } from '@yume-chan/event';
2
+ import type { Adb } from '../../adb.js';
3
+ import { AdbFeatures } from '../../features.js';
4
+ import type { AdbSocket } from '../../socket/index.js';
5
+ import { AdbBufferedStream, ReadableStream, WrapReadableStream, WrapWritableStream, WritableStream, WritableStreamDefaultWriter } from '../../stream/index.js';
6
+ import { AutoResetEvent } from '../../utils/index.js';
7
+ import { escapeArg } from "../index.js";
8
+ import { adbSyncOpenDir, type AdbSyncEntry } from './list.js';
9
+ import { adbSyncPull } from './pull.js';
10
+ import { adbSyncPush } from './push.js';
11
+ import { adbSyncLstat, adbSyncStat } from './stat.js';
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
+
29
+ export class AdbSync extends AutoDisposable {
30
+ protected adb: Adb;
31
+
32
+ protected stream: AdbBufferedStream;
33
+
34
+ protected writer: WritableStreamDefaultWriter<Uint8Array>;
35
+
36
+ protected sendLock = this.addDisposable(new AutoResetEvent());
37
+
38
+ public get supportsStat(): boolean {
39
+ return this.adb.features!.includes(AdbFeatures.StatV2);
40
+ }
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
+
55
+ public constructor(adb: Adb, socket: AdbSocket) {
56
+ super();
57
+
58
+ this.adb = adb;
59
+ this.stream = new AdbBufferedStream(socket);
60
+ this.writer = socket.writable.getWriter();
61
+ }
62
+
63
+ public async lstat(path: string) {
64
+ await this.sendLock.wait();
65
+
66
+ try {
67
+ return adbSyncLstat(this.stream, this.writer, path, this.supportsStat);
68
+ } finally {
69
+ this.sendLock.notify();
70
+ }
71
+ }
72
+
73
+ public async stat(path: string) {
74
+ if (!this.supportsStat) {
75
+ throw new Error('Not supported');
76
+ }
77
+
78
+ await this.sendLock.wait();
79
+
80
+ try {
81
+ return adbSyncStat(this.stream, this.writer, path);
82
+ } finally {
83
+ this.sendLock.notify();
84
+ }
85
+ }
86
+
87
+ public async isDirectory(path: string): Promise<boolean> {
88
+ try {
89
+ await this.lstat(path + '/');
90
+ return true;
91
+ } catch (e) {
92
+ return false;
93
+ }
94
+ }
95
+
96
+ public async *opendir(
97
+ path: string
98
+ ): AsyncGenerator<AdbSyncEntry, void, void> {
99
+ await this.sendLock.wait();
100
+
101
+ try {
102
+ yield* adbSyncOpenDir(this.stream, this.writer, path, this.supportsList2);
103
+ } finally {
104
+ this.sendLock.notify();
105
+ }
106
+ }
107
+
108
+ public async readdir(path: string) {
109
+ const results: AdbSyncEntry[] = [];
110
+ for await (const entry of this.opendir(path)) {
111
+ results.push(entry);
112
+ }
113
+ return results;
114
+ }
115
+
116
+ /**
117
+ * Read the content of a file on device.
118
+ *
119
+ * @param filename The full path of the file on device to read.
120
+ * @returns A `ReadableStream` that reads from the file.
121
+ */
122
+ public read(filename: string): ReadableStream<Uint8Array> {
123
+ return new WrapReadableStream({
124
+ start: async () => {
125
+ await this.sendLock.wait();
126
+ return adbSyncPull(this.stream, this.writer, filename);
127
+ },
128
+ close: async () => {
129
+ this.sendLock.notify();
130
+ },
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Write (or overwrite) a file on device.
136
+ *
137
+ * @param filename The full path of the file on device to write.
138
+ * @param mode The unix permissions of the file.
139
+ * @param mtime The modified time of the file.
140
+ * @returns A `WritableStream` that writes to the file.
141
+ */
142
+ public write(
143
+ filename: string,
144
+ mode?: number,
145
+ mtime?: number,
146
+ ): WritableStream<Uint8Array> {
147
+ return new WrapWritableStream({
148
+ start: async () => {
149
+ await this.sendLock.wait();
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
+ );
169
+ },
170
+ close: async () => {
171
+ this.sendLock.notify();
172
+ }
173
+ });
174
+ }
175
+
176
+ public override async dispose() {
177
+ super.dispose();
178
+ await this.stream.close();
179
+ await this.writer.close();
180
+ }
181
+ }