mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 02:39:26 +02:00
refactor(adb): further split sync module
This commit is contained in:
parent
65e0ba042e
commit
d887639efd
18 changed files with 482 additions and 419 deletions
|
@ -2,12 +2,10 @@ import { PromiseResolver } from '@yume-chan/async-operation-manager';
|
||||||
import { DisposableList } from '@yume-chan/event';
|
import { DisposableList } from '@yume-chan/event';
|
||||||
import { AdbAuthenticationHandler, AdbDefaultAuthenticators } from './auth';
|
import { AdbAuthenticationHandler, AdbDefaultAuthenticators } from './auth';
|
||||||
import { AdbBackend } from './backend';
|
import { AdbBackend } from './backend';
|
||||||
import { AdbReverseCommand, AdbTcpIpCommand } from './commands';
|
import { AdbFrameBuffer, AdbReverseCommand, AdbSync, AdbTcpIpCommand } from './commands';
|
||||||
import { AdbFeatures } from './features';
|
import { AdbFeatures } from './features';
|
||||||
import { FrameBuffer } from './framebuffer';
|
|
||||||
import { AdbCommand } from './packet';
|
import { AdbCommand } from './packet';
|
||||||
import { AdbBufferedStream, AdbPacketDispatcher, AdbReadableStream, AdbStream } from './stream';
|
import { AdbBufferedStream, AdbPacketDispatcher, AdbReadableStream, AdbStream } from './stream';
|
||||||
import { AdbSync } from './sync';
|
|
||||||
|
|
||||||
export enum AdbPropKey {
|
export enum AdbPropKey {
|
||||||
Product = 'ro.product.name',
|
Product = 'ro.product.name',
|
||||||
|
@ -175,15 +173,20 @@ export class Adb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sync(): Promise<AdbSync> {
|
public async getProp(key: string): Promise<string> {
|
||||||
const stream = await this.createStream('sync:');
|
const output = await this.shell('getprop', key);
|
||||||
return new AdbSync(stream, this);
|
return output.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async framebuffer(): Promise<FrameBuffer> {
|
public async sync(): Promise<AdbSync> {
|
||||||
|
const stream = await this.createStream('sync:');
|
||||||
|
return new AdbSync(this, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async framebuffer(): Promise<AdbFrameBuffer> {
|
||||||
const stream = await this.createStream('framebuffer:');
|
const stream = await this.createStream('framebuffer:');
|
||||||
const buffered = new AdbBufferedStream(stream);
|
const buffered = new AdbBufferedStream(stream);
|
||||||
return FrameBuffer.deserialize(buffered);
|
return AdbFrameBuffer.deserialize(buffered);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createStream(service: string): Promise<AdbStream> {
|
public async createStream(service: string): Promise<AdbStream> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Struct, StructValueType } from "@yume-chan/struct";
|
import { Struct, StructValueType } from "@yume-chan/struct";
|
||||||
|
|
||||||
export const FrameBuffer =
|
export const AdbFrameBuffer =
|
||||||
new Struct({ littleEndian: true })
|
new Struct({ littleEndian: true })
|
||||||
.uint32('version', undefined, 2 as const)
|
.uint32('version', undefined, 2 as const)
|
||||||
.uint32('bpp')
|
.uint32('bpp')
|
||||||
|
@ -18,4 +18,4 @@ export const FrameBuffer =
|
||||||
.uint32('alpha_length')
|
.uint32('alpha_length')
|
||||||
.arrayBuffer('data', { lengthField: 'size' });
|
.arrayBuffer('data', { lengthField: 'size' });
|
||||||
|
|
||||||
export type FrameBuffer = StructValueType<typeof FrameBuffer>;
|
export type AdbFrameBuffer = StructValueType<typeof AdbFrameBuffer>;
|
|
@ -1,3 +1,5 @@
|
||||||
export * from './base';
|
export * from './base';
|
||||||
export * from './tcpip';
|
export * from './framebuffer';
|
||||||
export * from './reverse';
|
export * from './reverse';
|
||||||
|
export * from './sync';
|
||||||
|
export * from './tcpip';
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { AutoDisposable } from '@yume-chan/event';
|
import { AutoDisposable } from '@yume-chan/event';
|
||||||
import { Struct } from '@yume-chan/struct';
|
import { Struct } from '@yume-chan/struct';
|
||||||
import { Adb } from '../adb';
|
|
||||||
import { AdbPacket } from '../packet';
|
import { AdbPacket } from '../packet';
|
||||||
import { AdbBufferedStream, AdbPacketDispatcher, AdbStream } from '../stream';
|
import { AdbBufferedStream, AdbIncomingStreamEventArgs, AdbPacketDispatcher, AdbStream } from '../stream';
|
||||||
import { AdbCommandBase } from './base';
|
|
||||||
|
|
||||||
export interface AdbReverseHandler {
|
export interface AdbReverseHandler {
|
||||||
onStream(packet: AdbPacket, stream: AdbStream): void;
|
onStream(packet: AdbPacket, stream: AdbStream): void;
|
||||||
|
@ -29,38 +27,22 @@ const AdbReverseErrorResponse =
|
||||||
});
|
});
|
||||||
|
|
||||||
export class AdbReverseCommand extends AutoDisposable {
|
export class AdbReverseCommand extends AutoDisposable {
|
||||||
private portToHandlerMap = new Map<number, AdbReverseHandler>();
|
protected portToHandlerMap = new Map<number, AdbReverseHandler>();
|
||||||
|
|
||||||
private devicePortToPortMap = new Map<number, number>();
|
protected devicePortToPortMap = new Map<number, number>();
|
||||||
|
|
||||||
private dispatcher: AdbPacketDispatcher;
|
protected dispatcher: AdbPacketDispatcher;
|
||||||
|
|
||||||
private listening = false;
|
protected listening = false;
|
||||||
|
|
||||||
public constructor(dispatcher: AdbPacketDispatcher) {
|
public constructor(dispatcher: AdbPacketDispatcher) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.dispatcher = dispatcher;
|
this.dispatcher = dispatcher;
|
||||||
|
this.addDisposable(this.dispatcher.onStream(this.handleStream, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async list(): Promise<AdbForwardListener[]> {
|
protected handleStream(e: AdbIncomingStreamEventArgs): void {
|
||||||
const stream = await this.dispatcher.createStream('reverse:list-forward');
|
|
||||||
const buffered = new AdbBufferedStream(stream);
|
|
||||||
|
|
||||||
const response = await AdbReverseStringResponse.deserialize(buffered);
|
|
||||||
|
|
||||||
return response.content!.split('\n').map(line => {
|
|
||||||
const [deviceSerial, localName, remoteName] = line.split(' ');
|
|
||||||
return { deviceSerial, localName, remoteName };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async add(
|
|
||||||
port: number,
|
|
||||||
handler: AdbReverseHandler,
|
|
||||||
devicePort: number = 0,
|
|
||||||
): Promise<number> {
|
|
||||||
if (!this.listening) {
|
|
||||||
this.addDisposable(this.dispatcher.onStream(e => {
|
|
||||||
if (e.handled) {
|
if (e.handled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -71,9 +53,13 @@ export class AdbReverseCommand extends AutoDisposable {
|
||||||
this.portToHandlerMap.get(port)!.onStream(e.packet, e.stream);
|
this.portToHandlerMap.get(port)!.onStream(e.packet, e.stream);
|
||||||
e.handled = true;
|
e.handled = true;
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async add(
|
||||||
|
port: number,
|
||||||
|
handler: AdbReverseHandler,
|
||||||
|
devicePort: number = 0,
|
||||||
|
): Promise<number> {
|
||||||
const stream = await this.dispatcher.createStream(`reverse:forward:tcp:${devicePort};tcp:${port}`);
|
const stream = await this.dispatcher.createStream(`reverse:forward:tcp:${devicePort};tcp:${port}`);
|
||||||
const buffered = new AdbBufferedStream(stream);
|
const buffered = new AdbBufferedStream(stream);
|
||||||
|
|
||||||
|
@ -92,6 +78,18 @@ export class AdbReverseCommand extends AutoDisposable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async list(): Promise<AdbForwardListener[]> {
|
||||||
|
const stream = await this.dispatcher.createStream('reverse:list-forward');
|
||||||
|
const buffered = new AdbBufferedStream(stream);
|
||||||
|
|
||||||
|
const response = await AdbReverseStringResponse.deserialize(buffered);
|
||||||
|
|
||||||
|
return response.content!.split('\n').map(line => {
|
||||||
|
const [deviceSerial, localName, remoteName] = line.split(' ');
|
||||||
|
return { deviceSerial, localName, remoteName };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async remove(devicePort: number): Promise<void> {
|
public async remove(devicePort: number): Promise<void> {
|
||||||
const stream = await this.dispatcher.createStream(`reverse:killforward:tcp:${devicePort}`);
|
const stream = await this.dispatcher.createStream(`reverse:killforward:tcp:${devicePort}`);
|
||||||
const buffered = new AdbBufferedStream(stream);
|
const buffered = new AdbBufferedStream(stream);
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
export * from './sync';
|
|
||||||
export * from './list';
|
export * from './list';
|
||||||
|
export * from './receive';
|
||||||
|
export * from './request';
|
||||||
|
export * from './response';
|
||||||
|
export * from './send';
|
||||||
|
export * from './stat';
|
||||||
|
export * from './sync';
|
||||||
|
|
39
packages/adb/src/commands/sync/list.ts
Normal file
39
packages/adb/src/commands/sync/list.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { StructValueType } from '@yume-chan/struct';
|
||||||
|
import { AdbBufferedStream } from '../../stream';
|
||||||
|
import { AdbSyncRequestId, adbSyncWriteRequest } from './request';
|
||||||
|
import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './response';
|
||||||
|
import { AdbSyncLstatResponse } from './stat';
|
||||||
|
|
||||||
|
export const AdbSyncEntryResponse =
|
||||||
|
AdbSyncLstatResponse
|
||||||
|
.afterParsed()
|
||||||
|
.uint32('nameLength')
|
||||||
|
.string('name', { lengthField: 'nameLength' })
|
||||||
|
.extra({ id: AdbSyncResponseId.Entry as const });
|
||||||
|
|
||||||
|
export type AdbSyncEntryResponse = StructValueType<typeof AdbSyncEntryResponse>;
|
||||||
|
|
||||||
|
const ResponseTypes = {
|
||||||
|
[AdbSyncResponseId.Entry]: AdbSyncEntryResponse,
|
||||||
|
[AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncEntryResponse.size),
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function* adbSyncOpenDir(
|
||||||
|
stream: AdbBufferedStream,
|
||||||
|
path: string
|
||||||
|
): AsyncGenerator<AdbSyncEntryResponse, void, void> {
|
||||||
|
await adbSyncWriteRequest(stream, AdbSyncRequestId.List, path);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const response = await adbSyncReadResponse(stream, ResponseTypes);
|
||||||
|
switch (response.id) {
|
||||||
|
case AdbSyncResponseId.Entry:
|
||||||
|
yield response;
|
||||||
|
break;
|
||||||
|
case AdbSyncResponseId.Done:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new Error('Unexpected response id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
packages/adb/src/commands/sync/receive.ts
Normal file
34
packages/adb/src/commands/sync/receive.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { Struct } from '@yume-chan/struct';
|
||||||
|
import { AdbBufferedStream } from '../../stream';
|
||||||
|
import { AdbSyncRequestId, adbSyncWriteRequest } from './request';
|
||||||
|
import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './response';
|
||||||
|
|
||||||
|
export const AdbSyncDataResponse =
|
||||||
|
new Struct({ littleEndian: true })
|
||||||
|
.uint32('dataLength')
|
||||||
|
.arrayBuffer('data', { lengthField: 'dataLength' })
|
||||||
|
.extra({ id: AdbSyncResponseId.Data as const });
|
||||||
|
|
||||||
|
const ResponseTypes = {
|
||||||
|
[AdbSyncResponseId.Data]: AdbSyncDataResponse,
|
||||||
|
[AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncDataResponse.size),
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function* adbSyncPull(
|
||||||
|
stream: AdbBufferedStream,
|
||||||
|
path: string,
|
||||||
|
): AsyncGenerator<ArrayBuffer, void, void> {
|
||||||
|
await adbSyncWriteRequest(stream, AdbSyncRequestId.Receive, path);
|
||||||
|
while (true) {
|
||||||
|
const response = await adbSyncReadResponse(stream, ResponseTypes);
|
||||||
|
switch (response.id) {
|
||||||
|
case AdbSyncResponseId.Data:
|
||||||
|
yield response.data!;
|
||||||
|
break;
|
||||||
|
case AdbSyncResponseId.Done:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new Error('Unexpected response id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
packages/adb/src/commands/sync/request.ts
Normal file
47
packages/adb/src/commands/sync/request.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { Struct } from '@yume-chan/struct';
|
||||||
|
import { AdbBufferedStream } from '../../stream';
|
||||||
|
|
||||||
|
export enum AdbSyncRequestId {
|
||||||
|
List = 'LIST',
|
||||||
|
Send = 'SEND',
|
||||||
|
Lstat = 'STAT',
|
||||||
|
Stat = 'STA2',
|
||||||
|
Lstat2 = 'LST2',
|
||||||
|
Data = 'DATA',
|
||||||
|
Done = 'DONE',
|
||||||
|
Receive = 'RECV',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdbSyncNumberRequest =
|
||||||
|
new Struct({ littleEndian: true })
|
||||||
|
.string('id', { length: 4 })
|
||||||
|
.uint32('arg');
|
||||||
|
|
||||||
|
export const AdbSyncDataRequest =
|
||||||
|
AdbSyncNumberRequest
|
||||||
|
.arrayBuffer('data', { lengthField: 'arg' });
|
||||||
|
|
||||||
|
export async function adbSyncWriteRequest(
|
||||||
|
stream: AdbBufferedStream,
|
||||||
|
id: AdbSyncRequestId | string,
|
||||||
|
value: number | string | ArrayBuffer
|
||||||
|
): Promise<void> {
|
||||||
|
let buffer: ArrayBuffer;
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
buffer = AdbSyncNumberRequest.serialize({
|
||||||
|
id,
|
||||||
|
arg: value,
|
||||||
|
}, stream);
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
buffer = AdbSyncDataRequest.serialize({
|
||||||
|
id,
|
||||||
|
data: stream.encodeUtf8(value),
|
||||||
|
}, stream);
|
||||||
|
} else {
|
||||||
|
buffer = AdbSyncDataRequest.serialize({
|
||||||
|
id,
|
||||||
|
data: value,
|
||||||
|
}, stream);
|
||||||
|
}
|
||||||
|
await stream.write(buffer);
|
||||||
|
}
|
57
packages/adb/src/commands/sync/response.ts
Normal file
57
packages/adb/src/commands/sync/response.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { Struct, StructDeserializationContext, StructValueType } from '@yume-chan/struct';
|
||||||
|
import { AdbBufferedStream } from '../../stream';
|
||||||
|
|
||||||
|
export enum AdbSyncResponseId {
|
||||||
|
Entry = 'DENT',
|
||||||
|
Lstat = 'STAT',
|
||||||
|
Stat = 'STA2',
|
||||||
|
Lstat2 = 'LST2',
|
||||||
|
Done = 'DONE',
|
||||||
|
Data = 'DATA',
|
||||||
|
Ok = 'OKAY',
|
||||||
|
Fail = 'FAIL',
|
||||||
|
}
|
||||||
|
|
||||||
|
// DONE responses' size are always same as the request's normal response.
|
||||||
|
// For example DONE responses for LIST requests are 16 bytes (same as DENT responses),
|
||||||
|
// but DONE responses for STAT requests are 12 bytes (same as STAT responses)
|
||||||
|
// So we need to know responses' size in advance.
|
||||||
|
export class AdbSyncDoneResponse {
|
||||||
|
private length: number;
|
||||||
|
|
||||||
|
public readonly id = AdbSyncResponseId.Done;
|
||||||
|
|
||||||
|
public constructor(length: number) {
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deserialize(context: StructDeserializationContext): Promise<this> {
|
||||||
|
await context.read(this.length);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdbSyncFailResponse =
|
||||||
|
new Struct({ littleEndian: true })
|
||||||
|
.uint32('messageLength')
|
||||||
|
.string('message', { lengthField: 'messageLength' })
|
||||||
|
.afterParsed(object => {
|
||||||
|
throw new Error(object.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function adbSyncReadResponse<T extends Record<string, { deserialize(context: StructDeserializationContext): Promise<any>; }>>(
|
||||||
|
stream: AdbBufferedStream,
|
||||||
|
types: T,
|
||||||
|
): Promise<StructValueType<T[keyof T]>> {
|
||||||
|
const id = stream.backend.decodeUtf8(await stream.read(4));
|
||||||
|
|
||||||
|
if (id === AdbSyncResponseId.Fail) {
|
||||||
|
await AdbSyncFailResponse.deserialize(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types[id]) {
|
||||||
|
return types[id].deserialize(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unexpected response id');
|
||||||
|
}
|
107
packages/adb/src/commands/sync/send.ts
Normal file
107
packages/adb/src/commands/sync/send.ts
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import { Struct } from '@yume-chan/struct';
|
||||||
|
import { AdbBufferedStream } from '../../stream';
|
||||||
|
import { AdbSyncRequestId, adbSyncWriteRequest } from './request';
|
||||||
|
import { adbSyncReadResponse, AdbSyncResponseId } from './response';
|
||||||
|
import { LinuxFileType } from './stat';
|
||||||
|
|
||||||
|
export const AdbSyncOkResponse =
|
||||||
|
new Struct({ littleEndian: true })
|
||||||
|
.uint32('unused');
|
||||||
|
|
||||||
|
const ResponseTypes = {
|
||||||
|
[AdbSyncResponseId.Ok]: AdbSyncOkResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function* chunkArrayLike(
|
||||||
|
value: ArrayLike<number> | ArrayBufferLike,
|
||||||
|
size: number
|
||||||
|
): Generator<ArrayBuffer, void, void> {
|
||||||
|
if ('length' in value) {
|
||||||
|
value = new Uint8Array(value).buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.byteLength <= size) {
|
||||||
|
return yield value;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < value.byteLength; i += size) {
|
||||||
|
yield value.slice(i, i + size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function* chunkAsyncIterable(
|
||||||
|
value: AsyncIterable<ArrayBuffer>,
|
||||||
|
size: number
|
||||||
|
): AsyncGenerator<ArrayBuffer, void, void> {
|
||||||
|
let result = new Uint8Array(size);
|
||||||
|
let index = 0;
|
||||||
|
for await (let buffer of value) {
|
||||||
|
// `result` has some data, `result + buffer` is enough
|
||||||
|
if (index !== 0 && index + buffer.byteLength >= size) {
|
||||||
|
const remainder = size - index;
|
||||||
|
result.set(new Uint8Array(buffer, 0, remainder), index);
|
||||||
|
yield result.buffer;
|
||||||
|
|
||||||
|
result = new Uint8Array(size);
|
||||||
|
index = 0;
|
||||||
|
|
||||||
|
if (buffer.byteLength > remainder) {
|
||||||
|
// `buffer` still has some data
|
||||||
|
buffer = buffer.slice(remainder);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `result` is empty, `buffer` alone is enough
|
||||||
|
if (buffer.byteLength >= size) {
|
||||||
|
let remainder = false;
|
||||||
|
for (const chunk of chunkArrayLike(buffer, size)) {
|
||||||
|
if (chunk.byteLength === size) {
|
||||||
|
yield chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `buffer` still has some data
|
||||||
|
remainder = true;
|
||||||
|
buffer = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!remainder) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `result` has some data but `result + buffer` is still not enough
|
||||||
|
// or after previous steps `buffer` still has some data
|
||||||
|
result.set(new Uint8Array(buffer), index);
|
||||||
|
index += buffer.byteLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdbSyncSendPacketSize = 64 * 1024;
|
||||||
|
|
||||||
|
export async function adbSyncPush(
|
||||||
|
stream: AdbBufferedStream,
|
||||||
|
path: string,
|
||||||
|
file: ArrayLike<number> | ArrayBufferLike | AsyncIterable<ArrayBuffer>,
|
||||||
|
mode: number = LinuxFileType.File | 0o777,
|
||||||
|
mtime: number = (Date.now() / 1000) | 0,
|
||||||
|
packetSize: number = AdbSyncSendPacketSize,
|
||||||
|
): Promise<void> {
|
||||||
|
const pathAndMode = `${path},${mode.toString(8)}`;
|
||||||
|
await adbSyncWriteRequest(stream, AdbSyncRequestId.Send, pathAndMode);
|
||||||
|
|
||||||
|
let chunkReader: Iterable<ArrayBuffer> | AsyncIterable<ArrayBuffer>;
|
||||||
|
if ('length' in file || 'byteLength' in file) {
|
||||||
|
chunkReader = chunkArrayLike(file, packetSize);
|
||||||
|
} else {
|
||||||
|
chunkReader = chunkAsyncIterable(file, packetSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const buffer of chunkReader) {
|
||||||
|
await adbSyncWriteRequest(stream, AdbSyncRequestId.Data, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
await adbSyncWriteRequest(stream, AdbSyncRequestId.Send, mtime);
|
||||||
|
await adbSyncReadResponse(stream, ResponseTypes);
|
||||||
|
}
|
121
packages/adb/src/commands/sync/stat.ts
Normal file
121
packages/adb/src/commands/sync/stat.ts
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import { placeholder, Struct, StructValueType } from '@yume-chan/struct';
|
||||||
|
import { AdbBufferedStream } from '../../stream';
|
||||||
|
import { AdbSyncRequestId, adbSyncWriteRequest } from './request';
|
||||||
|
import { adbSyncReadResponse, AdbSyncResponseId } from './response';
|
||||||
|
|
||||||
|
// https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36
|
||||||
|
export enum LinuxFileType {
|
||||||
|
Directory = 0o04,
|
||||||
|
File = 0o10,
|
||||||
|
Link = 0o12,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdbSyncLstatResponse =
|
||||||
|
new Struct({ littleEndian: true })
|
||||||
|
.int32('mode')
|
||||||
|
.int32('size')
|
||||||
|
.int32('mtime')
|
||||||
|
.extra({
|
||||||
|
id: AdbSyncResponseId.Lstat as const,
|
||||||
|
get type() { return this.mode >> 12 as LinuxFileType; },
|
||||||
|
get permission() { return this.mode & 0b00001111_11111111; },
|
||||||
|
})
|
||||||
|
.afterParsed((object) => {
|
||||||
|
if (object.mode === 0 &&
|
||||||
|
object.size === 0 &&
|
||||||
|
object.mtime === 0
|
||||||
|
) {
|
||||||
|
throw new Error('lstat failed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AdbSyncLstatResponse = StructValueType<typeof AdbSyncLstatResponse>;
|
||||||
|
|
||||||
|
export enum AdbSyncStatErrorCode {
|
||||||
|
EACCES = 13,
|
||||||
|
EEXIST = 17,
|
||||||
|
EFAULT = 14,
|
||||||
|
EFBIG = 27,
|
||||||
|
EINTR = 4,
|
||||||
|
EINVAL = 22,
|
||||||
|
EIO = 5,
|
||||||
|
EISDIR = 21,
|
||||||
|
ELOOP = 40,
|
||||||
|
EMFILE = 24,
|
||||||
|
ENAMETOOLONG = 36,
|
||||||
|
ENFILE = 23,
|
||||||
|
ENOENT = 2,
|
||||||
|
ENOMEM = 12,
|
||||||
|
ENOSPC = 28,
|
||||||
|
ENOTDIR = 20,
|
||||||
|
EOVERFLOW = 75,
|
||||||
|
EPERM = 1,
|
||||||
|
EROFS = 30,
|
||||||
|
ETXTBSY = 26,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdbSyncStatResponse =
|
||||||
|
new Struct({ littleEndian: true })
|
||||||
|
.uint32('error', undefined, placeholder<AdbSyncStatErrorCode>())
|
||||||
|
.uint64('dev')
|
||||||
|
.uint64('ino')
|
||||||
|
.uint32('mode')
|
||||||
|
.uint32('nlink')
|
||||||
|
.uint32('uid')
|
||||||
|
.uint32('gid')
|
||||||
|
.uint64('size')
|
||||||
|
.uint64('atime')
|
||||||
|
.uint64('mtime')
|
||||||
|
.uint64('ctime')
|
||||||
|
.extra({
|
||||||
|
id: AdbSyncResponseId.Stat as const,
|
||||||
|
get type() { return this.mode >> 12 as LinuxFileType; },
|
||||||
|
get permission() { return this.mode & 0b00001111_11111111; },
|
||||||
|
})
|
||||||
|
.afterParsed((object) => {
|
||||||
|
if (object.error) {
|
||||||
|
throw new Error(AdbSyncStatErrorCode[object.error]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AdbSyncStatResponse = StructValueType<typeof AdbSyncStatResponse>;
|
||||||
|
|
||||||
|
const StatResponseType = {
|
||||||
|
[AdbSyncResponseId.Stat]: AdbSyncStatResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
const LstatResponseType = {
|
||||||
|
[AdbSyncResponseId.Lstat]: AdbSyncLstatResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Lstat2ResponseType = {
|
||||||
|
[AdbSyncResponseId.Lstat2]: AdbSyncStatResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function adbSyncLstat(
|
||||||
|
stream: AdbBufferedStream,
|
||||||
|
path: string,
|
||||||
|
v2: boolean,
|
||||||
|
): Promise<AdbSyncLstatResponse | AdbSyncStatResponse> {
|
||||||
|
let requestId: AdbSyncRequestId.Lstat | AdbSyncRequestId.Lstat2;
|
||||||
|
let responseType: typeof LstatResponseType | typeof Lstat2ResponseType;
|
||||||
|
|
||||||
|
if (v2) {
|
||||||
|
requestId = AdbSyncRequestId.Lstat2;
|
||||||
|
responseType = Lstat2ResponseType;
|
||||||
|
} else {
|
||||||
|
requestId = AdbSyncRequestId.Lstat;
|
||||||
|
responseType = LstatResponseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
await adbSyncWriteRequest(stream, requestId, path);
|
||||||
|
return adbSyncReadResponse(stream, responseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function adbSyncStat(
|
||||||
|
stream: AdbBufferedStream,
|
||||||
|
path: string,
|
||||||
|
): Promise<AdbSyncStatResponse> {
|
||||||
|
await adbSyncWriteRequest(stream, AdbSyncRequestId.Stat, path);
|
||||||
|
return adbSyncReadResponse(stream, StatResponseType);
|
||||||
|
}
|
|
@ -1,260 +1,12 @@
|
||||||
import { AutoDisposable } from '@yume-chan/event';
|
import { AutoDisposable } from '@yume-chan/event';
|
||||||
import { placeholder, Struct, StructDeserializationContext, StructInitType, StructValueType } from '@yume-chan/struct';
|
|
||||||
import { Adb } from '../../adb';
|
import { Adb } from '../../adb';
|
||||||
import { AdbFeatures } from '../../features';
|
import { AdbFeatures } from '../../features';
|
||||||
import { AdbBufferedStream, AdbStream } from '../../stream';
|
import { AdbBufferedStream, AdbStream } from '../../stream';
|
||||||
import { AutoResetEvent } from '../../utils';
|
import { AutoResetEvent } from '../../utils';
|
||||||
|
import { AdbSyncEntryResponse, adbSyncOpenDir } from './list';
|
||||||
export enum AdbSyncRequestId {
|
import { adbSyncPull } from './receive';
|
||||||
List = 'LIST',
|
import { adbSyncPush } from './send';
|
||||||
Send = 'SEND',
|
import { adbSyncLstat, adbSyncStat } from './stat';
|
||||||
Lstat = 'STAT',
|
|
||||||
Stat = 'STA2',
|
|
||||||
Lstat2 = 'LST2',
|
|
||||||
Data = 'DATA',
|
|
||||||
Done = 'DONE',
|
|
||||||
Receive = 'RECV',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AdbSyncResponseId {
|
|
||||||
Entry = 'DENT',
|
|
||||||
Lstat = 'STAT',
|
|
||||||
Stat = 'STA2',
|
|
||||||
Lstat2 = 'LST2',
|
|
||||||
Done = 'DONE',
|
|
||||||
Data = 'DATA',
|
|
||||||
Ok = 'OKAY',
|
|
||||||
Fail = 'FAIL',
|
|
||||||
}
|
|
||||||
|
|
||||||
const AdbSyncNumberRequest =
|
|
||||||
new Struct({ littleEndian: true })
|
|
||||||
.string('id', { length: 4 })
|
|
||||||
.uint32('arg');
|
|
||||||
|
|
||||||
const AdbSyncStringRequest =
|
|
||||||
AdbSyncNumberRequest
|
|
||||||
.string('data', { lengthField: 'arg' });
|
|
||||||
|
|
||||||
const AdbSyncBufferRequest =
|
|
||||||
AdbSyncNumberRequest
|
|
||||||
.arrayBuffer('data', { lengthField: 'arg' });
|
|
||||||
|
|
||||||
// https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36
|
|
||||||
export enum LinuxFileType {
|
|
||||||
Directory = 0o04,
|
|
||||||
File = 0o10,
|
|
||||||
Link = 0o12,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AdbSyncLstatResponse =
|
|
||||||
new Struct({ littleEndian: true })
|
|
||||||
.int32('mode')
|
|
||||||
.int32('size')
|
|
||||||
.int32('mtime')
|
|
||||||
.extra({
|
|
||||||
id: AdbSyncResponseId.Lstat as const,
|
|
||||||
get type() { return this.mode >> 12 as LinuxFileType; },
|
|
||||||
get permission() { return this.mode & 0b00001111_11111111; },
|
|
||||||
})
|
|
||||||
.afterParsed((object) => {
|
|
||||||
if (object.mode === 0 &&
|
|
||||||
object.size === 0 &&
|
|
||||||
object.mtime === 0
|
|
||||||
) {
|
|
||||||
throw new Error('lstat failed');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AdbSyncLstatResponse = StructValueType<typeof AdbSyncLstatResponse>;
|
|
||||||
|
|
||||||
export enum ErrorCode {
|
|
||||||
EACCES = 13,
|
|
||||||
EEXIST = 17,
|
|
||||||
EFAULT = 14,
|
|
||||||
EFBIG = 27,
|
|
||||||
EINTR = 4,
|
|
||||||
EINVAL = 22,
|
|
||||||
EIO = 5,
|
|
||||||
EISDIR = 21,
|
|
||||||
ELOOP = 40,
|
|
||||||
EMFILE = 24,
|
|
||||||
ENAMETOOLONG = 36,
|
|
||||||
ENFILE = 23,
|
|
||||||
ENOENT = 2,
|
|
||||||
ENOMEM = 12,
|
|
||||||
ENOSPC = 28,
|
|
||||||
ENOTDIR = 20,
|
|
||||||
EOVERFLOW = 75,
|
|
||||||
EPERM = 1,
|
|
||||||
EROFS = 30,
|
|
||||||
ETXTBSY = 26,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AdbSyncStatResponse =
|
|
||||||
new Struct({ littleEndian: true })
|
|
||||||
.uint32('error', undefined, placeholder<ErrorCode>())
|
|
||||||
.uint64('dev')
|
|
||||||
.uint64('ino')
|
|
||||||
.uint32('mode')
|
|
||||||
.uint32('nlink')
|
|
||||||
.uint32('uid')
|
|
||||||
.uint32('gid')
|
|
||||||
.uint64('size')
|
|
||||||
.uint64('atime')
|
|
||||||
.uint64('mtime')
|
|
||||||
.uint64('ctime')
|
|
||||||
.extra({
|
|
||||||
id: AdbSyncResponseId.Stat as const,
|
|
||||||
get type() { return this.mode >> 12 as LinuxFileType; },
|
|
||||||
get permission() { return this.mode & 0b00001111_11111111; },
|
|
||||||
})
|
|
||||||
.afterParsed((object) => {
|
|
||||||
if (object.error) {
|
|
||||||
throw new Error(ErrorCode[object.error]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AdbSyncStatResponse = StructValueType<typeof AdbSyncStatResponse>;
|
|
||||||
|
|
||||||
export const AdbSyncEntryResponse =
|
|
||||||
AdbSyncLstatResponse
|
|
||||||
.afterParsed()
|
|
||||||
.uint32('nameLength')
|
|
||||||
.string('name', { lengthField: 'nameLength' })
|
|
||||||
.extra({ id: AdbSyncResponseId.Entry as const });
|
|
||||||
|
|
||||||
export type AdbSyncEntryResponse = StructValueType<typeof AdbSyncEntryResponse>;
|
|
||||||
|
|
||||||
export const AdbSyncDataResponse =
|
|
||||||
new Struct({ littleEndian: true })
|
|
||||||
.uint32('dataLength')
|
|
||||||
.arrayBuffer('data', { lengthField: 'dataLength' })
|
|
||||||
.extra({ id: AdbSyncResponseId.Data as const });
|
|
||||||
|
|
||||||
export interface AdbSyncDoneResponseDeserializeContext extends StructDeserializationContext {
|
|
||||||
size: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AdbSyncDoneResponse {
|
|
||||||
public static readonly instance = new AdbSyncDoneResponse();
|
|
||||||
|
|
||||||
public static async deserialize(
|
|
||||||
context: AdbSyncDoneResponseDeserializeContext
|
|
||||||
): Promise<AdbSyncDoneResponse> {
|
|
||||||
await context.read(context.size);
|
|
||||||
return AdbSyncDoneResponse.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly id = AdbSyncResponseId.Done;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AdbSyncFailResponse =
|
|
||||||
new Struct({ littleEndian: true })
|
|
||||||
.uint32('messageLength')
|
|
||||||
.string('message', { lengthField: 'messageLength' })
|
|
||||||
.afterParsed(object => {
|
|
||||||
throw new Error(object.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
const ResponseTypeMap = {
|
|
||||||
[AdbSyncResponseId.Entry]: AdbSyncEntryResponse,
|
|
||||||
[AdbSyncResponseId.Lstat]: AdbSyncLstatResponse,
|
|
||||||
[AdbSyncResponseId.Stat]: AdbSyncStatResponse,
|
|
||||||
[AdbSyncResponseId.Lstat2]: AdbSyncStatResponse,
|
|
||||||
[AdbSyncResponseId.Data]: AdbSyncDataResponse,
|
|
||||||
[AdbSyncResponseId.Fail]: AdbSyncFailResponse,
|
|
||||||
[AdbSyncResponseId.Done]: AdbSyncDoneResponse,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
async function readResponse(stream: AdbBufferedStream, size: number) {
|
|
||||||
// DONE responses' size are always same as the request's normal response.
|
|
||||||
// For example DONE responses for LIST requests are 16 bytes (same as DENT responses),
|
|
||||||
// but DONE responses for STAT requests are 12 bytes (same as STAT responses)
|
|
||||||
// So we need to know responses' size in advance.
|
|
||||||
const id = stream.backend.decodeUtf8(await stream.read(4)) as keyof typeof ResponseTypeMap;
|
|
||||||
|
|
||||||
if (ResponseTypeMap[id]) {
|
|
||||||
return ResponseTypeMap[id].deserialize({
|
|
||||||
size,
|
|
||||||
read: stream.read.bind(stream),
|
|
||||||
decodeUtf8: stream.backend.decodeUtf8.bind(stream.backend),
|
|
||||||
encodeUtf8: stream.backend.encodeUtf8.bind(stream.backend),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await stream.read(size);
|
|
||||||
throw new Error('Unexpected response id');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function chunkArray(
|
|
||||||
value: ArrayLike<number>,
|
|
||||||
size: number
|
|
||||||
): Generator<ArrayBuffer, void, void> {
|
|
||||||
return chunkArrayBuffer(new Uint8Array(value).buffer, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* chunkArrayBuffer(
|
|
||||||
value: ArrayBufferLike,
|
|
||||||
size: number
|
|
||||||
): Generator<ArrayBuffer, void, void> {
|
|
||||||
if (value.byteLength <= size) {
|
|
||||||
return yield value;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < value.byteLength; i += size) {
|
|
||||||
yield value.slice(i, i + size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function* chunkAsyncIterable(
|
|
||||||
value: AsyncIterable<ArrayBuffer>,
|
|
||||||
size: number
|
|
||||||
): AsyncGenerator<ArrayBuffer, void, void> {
|
|
||||||
let result = new Uint8Array(size);
|
|
||||||
let index = 0;
|
|
||||||
for await (let buffer of value) {
|
|
||||||
// `result` has some data, `result + buffer` is enough
|
|
||||||
if (index !== 0 && index + buffer.byteLength >= size) {
|
|
||||||
const remainder = size - index;
|
|
||||||
result.set(new Uint8Array(buffer, 0, remainder), index);
|
|
||||||
yield result.buffer;
|
|
||||||
|
|
||||||
result = new Uint8Array(size);
|
|
||||||
index = 0;
|
|
||||||
|
|
||||||
if (buffer.byteLength > remainder) {
|
|
||||||
// `buffer` still has some data
|
|
||||||
buffer = buffer.slice(remainder);
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// `result` is empty, `buffer` alone is enough
|
|
||||||
if (buffer.byteLength >= size) {
|
|
||||||
let remainder = false;
|
|
||||||
for (const chunk of chunkArrayBuffer(buffer, size)) {
|
|
||||||
if (chunk.byteLength === size) {
|
|
||||||
yield chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
// `buffer` still has some data
|
|
||||||
remainder = true;
|
|
||||||
buffer = chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!remainder) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// `result` has some data but `result + buffer` is still not enough
|
|
||||||
// or after previous steps `buffer` still has some data
|
|
||||||
result.set(new Uint8Array(buffer), index);
|
|
||||||
index += buffer.byteLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AdbSync extends AutoDisposable {
|
export class AdbSync extends AutoDisposable {
|
||||||
protected adb: Adb;
|
protected adb: Adb;
|
||||||
|
@ -274,37 +26,11 @@ export class AdbSync extends AutoDisposable {
|
||||||
this.stream = new AdbBufferedStream(stream);
|
this.stream = new AdbBufferedStream(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected send<T extends Struct<object, object, object, unknown>>(
|
public async lstat(path: string) {
|
||||||
type: T,
|
|
||||||
value: StructInitType<T>
|
|
||||||
) {
|
|
||||||
return this.stream.write(type.serialize(value, this.stream.backend));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async lstat(path: string): Promise<AdbSyncLstatResponse | AdbSyncStatResponse> {
|
|
||||||
await this.sendLock.wait();
|
await this.sendLock.wait();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let requestId: AdbSyncRequestId.Lstat | AdbSyncRequestId.Lstat2;
|
return adbSyncLstat(this.stream, path, this.supportStat);
|
||||||
let responseType: typeof AdbSyncLstatResponse | typeof AdbSyncStatResponse;
|
|
||||||
let responseId: AdbSyncResponseId.Lstat | AdbSyncResponseId.Stat;
|
|
||||||
|
|
||||||
if (this.supportStat) {
|
|
||||||
requestId = AdbSyncRequestId.Lstat2;
|
|
||||||
responseType = AdbSyncStatResponse;
|
|
||||||
responseId = AdbSyncResponseId.Stat;
|
|
||||||
} else {
|
|
||||||
requestId = AdbSyncRequestId.Lstat;
|
|
||||||
responseType = AdbSyncLstatResponse;
|
|
||||||
responseId = AdbSyncResponseId.Lstat;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.send(AdbSyncStringRequest, { id: requestId, data: path });
|
|
||||||
const response = await readResponse(this.stream, responseType.size);
|
|
||||||
if (response.id !== responseId) {
|
|
||||||
throw new Error('Unexpected response id');
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
} finally {
|
} finally {
|
||||||
this.sendLock.notify();
|
this.sendLock.notify();
|
||||||
}
|
}
|
||||||
|
@ -318,48 +44,28 @@ export class AdbSync extends AutoDisposable {
|
||||||
await this.sendLock.wait();
|
await this.sendLock.wait();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.send(AdbSyncStringRequest, { id: AdbSyncRequestId.Stat, data: path });
|
return adbSyncStat(this.stream, path);
|
||||||
const response = await readResponse(this.stream, AdbSyncStatResponse.size);
|
|
||||||
if (response.id !== AdbSyncResponseId.Stat) {
|
|
||||||
throw new Error('Unexpected response id');
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
} finally {
|
} finally {
|
||||||
this.sendLock.notify();
|
this.sendLock.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async isDirectory(path: string): Promise<boolean> {
|
public async isDirectory(path: string): Promise<boolean> {
|
||||||
await this.sendLock.wait();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.stat(path + '/');
|
await this.stat(path + '/');
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
|
||||||
this.sendLock.notify();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async *opendir(path: string) {
|
public async *opendir(
|
||||||
|
path: string
|
||||||
|
): AsyncGenerator<AdbSyncEntryResponse, void, void> {
|
||||||
await this.sendLock.wait();
|
await this.sendLock.wait();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.send(AdbSyncStringRequest, { id: AdbSyncRequestId.List, data: path });
|
yield* adbSyncOpenDir(this.stream, path);
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const response = await readResponse(this.stream, AdbSyncEntryResponse.size);
|
|
||||||
switch (response.id) {
|
|
||||||
case AdbSyncResponseId.Entry:
|
|
||||||
yield response;
|
|
||||||
break;
|
|
||||||
case AdbSyncResponseId.Done:
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
throw new Error('Unexpected response id');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
this.sendLock.notify();
|
this.sendLock.notify();
|
||||||
}
|
}
|
||||||
|
@ -377,36 +83,12 @@ export class AdbSync extends AutoDisposable {
|
||||||
await this.sendLock.wait();
|
await this.sendLock.wait();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.send(AdbSyncStringRequest, { id: AdbSyncRequestId.Receive, data: path });
|
yield* adbSyncPull(this.stream, path);
|
||||||
while (true) {
|
|
||||||
const response = await readResponse(this.stream, AdbSyncDataResponse.size);
|
|
||||||
switch (response.id) {
|
|
||||||
case AdbSyncResponseId.Data:
|
|
||||||
yield response.data!;
|
|
||||||
break;
|
|
||||||
case AdbSyncResponseId.Done:
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
throw new Error('Unexpected response id');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
this.sendLock.notify();
|
this.sendLock.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async write(
|
|
||||||
path: string,
|
|
||||||
file: AsyncIterable<ArrayBuffer>,
|
|
||||||
mode?: number,
|
|
||||||
mtime?: number,
|
|
||||||
): Promise<void>;
|
|
||||||
public async write(
|
|
||||||
path: string,
|
|
||||||
file: ArrayLike<number>,
|
|
||||||
mode?: number,
|
|
||||||
mtime?: number,
|
|
||||||
): Promise<void>;
|
|
||||||
public async write(
|
public async write(
|
||||||
path: string,
|
path: string,
|
||||||
file: AsyncIterable<ArrayBuffer> | ArrayLike<number>,
|
file: AsyncIterable<ArrayBuffer> | ArrayLike<number>,
|
||||||
|
@ -414,35 +96,9 @@ export class AdbSync extends AutoDisposable {
|
||||||
mtime = Date.now(),
|
mtime = Date.now(),
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.sendLock.wait();
|
await this.sendLock.wait();
|
||||||
const packetSize = 64 * 1024;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pathAndMode = `${path},${mode.toString(8)}`;
|
adbSyncPush(this.stream, path, file, mode, mtime);
|
||||||
await this.send(AdbSyncStringRequest, {
|
|
||||||
id: AdbSyncRequestId.Send,
|
|
||||||
data: pathAndMode
|
|
||||||
});
|
|
||||||
|
|
||||||
let chunkReader: Iterable<ArrayBuffer> | AsyncIterable<ArrayBuffer>;
|
|
||||||
if ('length' in file) {
|
|
||||||
chunkReader = chunkArray(file, packetSize);
|
|
||||||
} else if ('byteLength' in file) {
|
|
||||||
chunkReader = chunkArrayBuffer(file, packetSize);
|
|
||||||
} else {
|
|
||||||
chunkReader = chunkAsyncIterable(file, packetSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (const buffer of chunkReader) {
|
|
||||||
await this.send(AdbSyncBufferRequest, {
|
|
||||||
id: AdbSyncRequestId.Data,
|
|
||||||
data: buffer,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.send(AdbSyncNumberRequest, {
|
|
||||||
id: AdbSyncRequestId.Send,
|
|
||||||
arg: mtime
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
this.sendLock.notify();
|
this.sendLock.notify();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,18 @@
|
||||||
import { AdbCommandBase } from './base';
|
import { AdbCommandBase } from './base';
|
||||||
|
|
||||||
export class AdbTcpIpCommand extends AdbCommandBase {
|
export class AdbTcpIpCommand extends AdbCommandBase {
|
||||||
private async getProp(key: string): Promise<string> {
|
|
||||||
const output = await this.adb.shell('getprop', key);
|
|
||||||
return output.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAddresses(): Promise<string[]> {
|
public async getAddresses(): Promise<string[]> {
|
||||||
const propAddr = await this.getProp('service.adb.listen_addrs');
|
const propAddr = await this.adb.getProp('service.adb.listen_addrs');
|
||||||
if (propAddr) {
|
if (propAddr) {
|
||||||
return propAddr.split(',');
|
return propAddr.split(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
let port = await this.getProp('service.adb.tcp.port');
|
let port = await this.adb.getProp('service.adb.tcp.port');
|
||||||
if (port) {
|
if (port) {
|
||||||
return [`0.0.0.0:${port}`];
|
return [`0.0.0.0:${port}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
port = await this.getProp('persist.adb.tcp.port');
|
port = await this.adb.getProp('persist.adb.tcp.port');
|
||||||
if (port) {
|
if (port) {
|
||||||
return [`0.0.0.0:${port}`];
|
return [`0.0.0.0:${port}`];
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,7 +257,7 @@ export function sign(privateKey: ArrayBuffer, data: ArrayBuffer): ArrayBuffer {
|
||||||
const fillLength = padded.length - Sha1DigestInfo.length - data.byteLength - 1;
|
const fillLength = padded.length - Sha1DigestInfo.length - data.byteLength - 1;
|
||||||
while (index < fillLength) {
|
while (index < fillLength) {
|
||||||
padded[index] = 0xff;
|
padded[index] = 0xff;
|
||||||
index++;
|
index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
padded[index] = 0;
|
padded[index] = 0;
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
export * from './adb';
|
export * from './adb';
|
||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './backend';
|
export * from './backend';
|
||||||
|
export * from './commands';
|
||||||
export * from './crypto';
|
export * from './crypto';
|
||||||
export * from './features';
|
export * from './features';
|
||||||
export * from './framebuffer';
|
|
||||||
export * from './packet';
|
export * from './packet';
|
||||||
export * from './stream';
|
export * from './stream';
|
||||||
export * from './sync';
|
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
|
|
@ -11,7 +11,7 @@ export interface AdbPacketReceivedEventArgs {
|
||||||
packet: AdbPacket;
|
packet: AdbPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdbStreamCreatedEventArgs {
|
export interface AdbIncomingStreamEventArgs {
|
||||||
handled: boolean;
|
handled: boolean;
|
||||||
|
|
||||||
packet: AdbPacket;
|
packet: AdbPacket;
|
||||||
|
@ -30,7 +30,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
private readonly packetEvent = this.addDisposable(new EventEmitter<AdbPacketReceivedEventArgs>());
|
private readonly packetEvent = this.addDisposable(new EventEmitter<AdbPacketReceivedEventArgs>());
|
||||||
public get onPacket() { return this.packetEvent.event; }
|
public get onPacket() { return this.packetEvent.event; }
|
||||||
|
|
||||||
private readonly streamEvent = this.addDisposable(new EventEmitter<AdbStreamCreatedEventArgs>());
|
private readonly streamEvent = this.addDisposable(new EventEmitter<AdbIncomingStreamEventArgs>());
|
||||||
public get onStream() { return this.streamEvent.event; }
|
public get onStream() { return this.streamEvent.event; }
|
||||||
|
|
||||||
private readonly errorEvent = this.addDisposable(new EventEmitter<Error>());
|
private readonly errorEvent = this.addDisposable(new EventEmitter<Error>());
|
||||||
|
@ -132,7 +132,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
const controller = new AdbStreamController(localId, remoteId, this);
|
const controller = new AdbStreamController(localId, remoteId, this);
|
||||||
const stream = new AdbStream(controller);
|
const stream = new AdbStream(controller);
|
||||||
|
|
||||||
const args: AdbStreamCreatedEventArgs = {
|
const args: AdbIncomingStreamEventArgs = {
|
||||||
handled: false,
|
handled: false,
|
||||||
packet,
|
packet,
|
||||||
stream,
|
stream,
|
||||||
|
|
|
@ -18,7 +18,7 @@ function addRange(start: string, end: string) {
|
||||||
const endCharCode = end.charCodeAt(0);
|
const endCharCode = end.charCodeAt(0);
|
||||||
const length = endCharCode - startCharCode + 1;
|
const length = endCharCode - startCharCode + 1;
|
||||||
|
|
||||||
for (let i = startCharCode; i <= endCharCode; i++) {
|
for (let i = startCharCode; i <= endCharCode; i += 1) {
|
||||||
chars.push(i);
|
chars.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { Array, FieldDescriptorBase, FieldDescriptorBaseOptions, FieldType, Fiel
|
||||||
import { StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext } from './types';
|
import { StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext } from './types';
|
||||||
import { Evaluate, Identity, OmitNever, Overwrite } from './utils';
|
import { Evaluate, Identity, OmitNever, Overwrite } from './utils';
|
||||||
|
|
||||||
export type StructValueType<T extends Struct<object, object, object, unknown>> =
|
export type StructValueType<T> =
|
||||||
T extends { deserialize(reader: StructDeserializationContext): Promise<infer R>; } ? R : never;
|
T extends { deserialize(context: StructDeserializationContext): Promise<infer R>; } ? R : never;
|
||||||
|
|
||||||
export type StructInitType<T extends Struct<object, object, object, unknown>> =
|
export type StructInitType<T extends Struct<object, object, object, unknown>> =
|
||||||
T extends { create(value: infer R, ...args: any): any; } ? Evaluate<R> : never;
|
T extends { create(value: infer R, ...args: any): any; } ? Evaluate<R> : never;
|
||||||
|
@ -407,7 +407,7 @@ export default class Struct<
|
||||||
|
|
||||||
let size = this._size;
|
let size = this._size;
|
||||||
let fieldSize: number[] = [];
|
let fieldSize: number[] = [];
|
||||||
for (let i = 0; i < this.fields.length; i++) {
|
for (let i = 0; i < this.fields.length; i += 1) {
|
||||||
const field = this.fields[i];
|
const field = this.fields[i];
|
||||||
const type = getFieldTypeDefinition(field.type);
|
const type = getFieldTypeDefinition(field.type);
|
||||||
if (type.getDynamicSize) {
|
if (type.getDynamicSize) {
|
||||||
|
@ -426,7 +426,7 @@ export default class Struct<
|
||||||
const buffer = new ArrayBuffer(size);
|
const buffer = new ArrayBuffer(size);
|
||||||
const dataView = new DataView(buffer);
|
const dataView = new DataView(buffer);
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for (let i = 0; i < this.fields.length; i++) {
|
for (let i = 0; i < this.fields.length; i += 1) {
|
||||||
const field = this.fields[i];
|
const field = this.fields[i];
|
||||||
const type = getFieldTypeDefinition(field.type);
|
const type = getFieldTypeDefinition(field.type);
|
||||||
type.serialize({
|
type.serialize({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue