feat: stream migrate done

This commit is contained in:
Simon Chan 2022-02-18 11:36:10 +08:00
parent ef57682ec3
commit 2a5843eb20
14 changed files with 228 additions and 150 deletions

View file

@ -94,8 +94,8 @@ function _Connect(): JSX.Element | null {
}, []); }, []);
const addTcpBackend = useCallback(() => { const addTcpBackend = useCallback(() => {
const address = window.prompt('Enter the address of device'); const host = window.prompt('Enter the address of device');
if (!address) { if (!host) {
return; return;
} }
@ -108,8 +108,18 @@ function _Connect(): JSX.Element | null {
setTcpBackendList(list => { setTcpBackendList(list => {
const copy = list.slice(); const copy = list.slice();
copy.push(new AdbDirectSocketsBackend(address, portNumber)); copy.push(new AdbDirectSocketsBackend(host, portNumber));
window.localStorage.setItem('tcp-backend-list', JSON.stringify(copy.map(x => ({ address: x.address, port: x.port })))); window.localStorage.setItem(
'tcp-backend-list',
JSON.stringify(
copy.map(
x => ({
address: x.host,
port: x.port
})
)
)
);
return copy; return copy;
}); });
}, []); }, []);
@ -130,13 +140,14 @@ function _Connect(): JSX.Element | null {
const connect = useCallback(async () => { const connect = useCallback(async () => {
try { try {
if (selectedBackend) { if (selectedBackend) {
const device = new Adb(selectedBackend, logger.logger); let device: Adb | undefined;
try { try {
setConnecting(true); setConnecting(true);
await device.connect(CredentialStore); device = await Adb.connect(selectedBackend, logger.logger);
await device.authenticate(CredentialStore);
globalState.setDevice(device); globalState.setDevice(device);
} catch (e) { } catch (e) {
device.dispose(); device?.dispose();
throw e; throw e;
} }
} }

View file

@ -21,6 +21,7 @@ module.exports = withMDX({
reactStrictMode: true, reactStrictMode: true,
productionBrowserSourceMaps: true, productionBrowserSourceMaps: true,
experimental: { experimental: {
// Workaround https://github.com/vercel/next.js/issues/33914
esmExternals: 'loose', esmExternals: 'loose',
}, },
publicRuntimeConfig: { publicRuntimeConfig: {
@ -35,6 +36,8 @@ module.exports = withMDX({
}, },
}); });
config.experiments.topLevelAwait = true;
return config; return config;
}, },
async headers() { async headers() {

View file

@ -27,7 +27,7 @@
"@yume-chan/struct": "^0.0.10", "@yume-chan/struct": "^0.0.10",
"mobx": "^6.3.13", "mobx": "^6.3.13",
"mobx-react-lite": "^3.2.3", "mobx-react-lite": "^3.2.3",
"next": "12.0.11-canary.9", "next": "12.1.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"streamsaver": "^2.0.5", "streamsaver": "^2.0.5",
@ -41,7 +41,7 @@
"@next/mdx": "^11.1.2", "@next/mdx": "^11.1.2",
"@types/react": "17.0.27", "@types/react": "17.0.27",
"eslint": "8.8.0", "eslint": "8.8.0",
"eslint-config-next": "12.0.11-canary.6", "eslint-config-next": "12.1.0",
"typescript": "^4.5.5" "typescript": "^4.5.5"
} }
} }

View file

@ -3,7 +3,8 @@ import { FileIconType } from "@fluentui/react-file-type-icons";
import { getFileTypeIconNameFromExtensionOrType } from '@fluentui/react-file-type-icons/lib-commonjs/getFileTypeIconProps'; import { getFileTypeIconNameFromExtensionOrType } from '@fluentui/react-file-type-icons/lib-commonjs/getFileTypeIconProps';
import { DEFAULT_BASE_URL as FILE_TYPE_ICONS_BASE_URL } from '@fluentui/react-file-type-icons/lib-commonjs/initializeFileTypeIcons'; import { DEFAULT_BASE_URL as FILE_TYPE_ICONS_BASE_URL } from '@fluentui/react-file-type-icons/lib-commonjs/initializeFileTypeIcons';
import { useConst } from '@fluentui/react-hooks'; import { useConst } from '@fluentui/react-hooks';
import { AdbSyncEntryResponse, ADB_SYNC_MAX_PACKET_SIZE, LinuxFileType } from '@yume-chan/adb'; import { AdbSyncEntryResponse, ADB_SYNC_MAX_PACKET_SIZE, ChunkStream, LinuxFileType, ReadableStream, TransformStream } from '@yume-chan/adb';
import { ExtractViewBufferStream } from "@yume-chan/adb-backend-direct-sockets";
import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx"; import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { NextPage } from "next"; import { NextPage } from "next";
@ -11,10 +12,10 @@ import getConfig from "next/config";
import Head from "next/head"; import Head from "next/head";
import Router, { useRouter } from "next/router"; import Router, { useRouter } from "next/router";
import path from 'path'; import path from 'path';
import React, { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { CommandBar, NoSsr } from '../components'; import { CommandBar, NoSsr } from '../components';
import { globalState } from '../state'; import { globalState } from '../state';
import { asyncEffect, chunkFile, formatSize, formatSpeed, Icons, pickFile, RouteStackProps } from '../utils'; import { asyncEffect, formatSize, formatSpeed, Icons, pickFile, RouteStackProps } from '../utils';
/** /**
* Because of internal buffer of upstream/downstream streams, * Because of internal buffer of upstream/downstream streams,
@ -186,12 +187,12 @@ class FileManagerState {
const sync = await globalState.device!.sync(); const sync = await globalState.device!.sync();
try { try {
const itemPath = path.resolve(this.path, this.selectedItems[0].name!); const itemPath = path.resolve(this.path, this.selectedItems[0].name!);
const readableStream = sync.read(itemPath); const readableStream = await sync.read(itemPath);
const writeableStream = StreamSaver!.createWriteStream(this.selectedItems[0].name!, { const writeable = StreamSaver!.createWriteStream(this.selectedItems[0].name!, {
size: this.selectedItems[0].size, size: this.selectedItems[0].size,
}); });
await readableStream.pipeTo(writeableStream); await readableStream.pipeTo(writeable);
} catch (e) { } catch (e) {
globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`); globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
} finally { } finally {
@ -490,17 +491,18 @@ class FileManagerState {
}), 1000); }), 1000);
try { try {
const writable = sync.write( await (file.stream() as unknown as ReadableStream<Uint8Array>)
itemPath, .pipeThrough(new ExtractViewBufferStream())
chunkFile(file, ADB_SYNC_MAX_PACKET_SIZE), .pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
(LinuxFileType.File << 12) | 0o666, .pipeThrough(new ProgressStream(action((uploaded) => {
file.lastModified / 1000,
action((uploaded) => {
this.uploadedSize = uploaded; this.uploadedSize = uploaded;
}), })))
); .pipeTo(sync.write(
const readable: ReadableStream<ArrayBuffer> = file.stream(); itemPath,
readable.pipeThrough(); (LinuxFileType.File << 12) | 0o666,
file.lastModified / 1000,
));
runInAction(() => { runInAction(() => {
this.uploadSpeed = this.uploadedSize - this.debouncedUploadedSize; this.uploadSpeed = this.uploadedSize - this.debouncedUploadedSize;
this.debouncedUploadedSize = this.uploadedSize; this.debouncedUploadedSize = this.uploadedSize;
@ -571,8 +573,8 @@ const FileManager: NextPage = (): JSX.Element | null => {
const previewImage = useCallback(async (path: string) => { const previewImage = useCallback(async (path: string) => {
const sync = await globalState.device!.sync(); const sync = await globalState.device!.sync();
try { try {
const readableStream = createReadableStreamFromBufferIterator(sync.read(path)); const readable = await sync.read(path);
const response = new Response(readableStream); const response = new Response(readable);
const blob = await response.blob(); const blob = await response.blob();
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
setPreviewUrl(url); setPreviewUrl(url);

View file

@ -1,6 +1,7 @@
import { DefaultButton, ProgressIndicator, Stack } from "@fluentui/react"; import { DefaultButton, ProgressIndicator, Stack } from "@fluentui/react";
import { ADB_SYNC_MAX_PACKET_SIZE } from "@yume-chan/adb"; import { ADB_SYNC_MAX_PACKET_SIZE, ChunkStream, ReadableStream } from "@yume-chan/adb";
import { makeAutoObservable, observable, runInAction } from "mobx"; import { ExtractViewBufferStream } from "@yume-chan/adb-backend-direct-sockets";
import { action, makeAutoObservable, observable, runInAction } from "mobx";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { NextPage } from "next"; import { NextPage } from "next";
import Head from "next/head"; import Head from "next/head";
@ -58,10 +59,10 @@ class InstallPageState {
}; };
}); });
setTimeout(handler); await (file.stream() as unknown as ReadableStream<Uint8Array>)
const readable = file.stream(); .pipeThrough(new ExtractViewBufferStream())
await globalState.device!.install(chunkFile(file, AdbSyncMaxPacketSize), uploaded => { .pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
runInAction(() => { .pipeThrough(new ProgressStream(action((uploaded) => {
if (uploaded !== file.size) { if (uploaded !== file.size) {
this.progress = { this.progress = {
filename: file.name, filename: file.name,
@ -79,8 +80,8 @@ class InstallPageState {
value: 0.8, value: 0.8,
}; };
} }
}); })))
}); .pipeTo(globalState.device!.install());
runInAction(() => { runInAction(() => {
this.progress = { this.progress = {

View file

@ -17,9 +17,6 @@ export class GlobalState {
setDevice(device: Adb | undefined) { setDevice(device: Adb | undefined) {
this.device = device; this.device = device;
device?.onDisconnected(() => {
this.setDevice(undefined);
});
} }
showErrorDialog(message: string) { showErrorDialog(message: string) {

View file

@ -35,7 +35,7 @@ specifiers:
bluebird: ^3.7.2 bluebird: ^3.7.2
clsx: ^1.1.1 clsx: ^1.1.1
eslint: 8.8.0 eslint: 8.8.0
eslint-config-next: 12.0.11-canary.6 eslint-config-next: 12.1.0
file-loader: ^6.2.0 file-loader: ^6.2.0
gh-release-fetch: ^2.0.4 gh-release-fetch: ^2.0.4
jest: ^26.6.3 jest: ^26.6.3
@ -43,7 +43,7 @@ specifiers:
mini-svg-data-uri: ~1.3.3 mini-svg-data-uri: ~1.3.3
mobx: ^6.3.13 mobx: ^6.3.13
mobx-react-lite: ^3.2.3 mobx-react-lite: ^3.2.3
next: 12.0.11-canary.9 next: 12.1.0
node-fetch: ~2.6.1 node-fetch: ~2.6.1
plantuml-encoder: ~1.4.0 plantuml-encoder: ~1.4.0
react: ^17.0.2 react: ^17.0.2
@ -94,7 +94,7 @@ dependencies:
bluebird: 3.7.2 bluebird: 3.7.2
clsx: 1.1.1 clsx: 1.1.1
eslint: 8.8.0 eslint: 8.8.0
eslint-config-next: 12.0.11-canary.6_6a190f5566611a51beae5b8059e6f717 eslint-config-next: 12.1.0_eslint@8.8.0+next@12.1.0
file-loader: 6.2.0 file-loader: 6.2.0
gh-release-fetch: 2.0.6 gh-release-fetch: 2.0.6
jest: 26.6.3 jest: 26.6.3
@ -102,7 +102,7 @@ dependencies:
mini-svg-data-uri: 1.3.3 mini-svg-data-uri: 1.3.3
mobx: 6.3.13 mobx: 6.3.13
mobx-react-lite: 3.2.3_96b0034b8b6bfac1b4cc60715db17f02 mobx-react-lite: 3.2.3_96b0034b8b6bfac1b4cc60715db17f02
next: 12.0.11-canary.9_react-dom@17.0.2+react@17.0.2 next: 12.1.0_react-dom@17.0.2+react@17.0.2
node-fetch: 2.6.7 node-fetch: 2.6.7
plantuml-encoder: 1.4.0 plantuml-encoder: 1.4.0
react: 17.0.2 react: 17.0.2
@ -2988,12 +2988,12 @@ packages:
resolution: {integrity: sha512-vKbuG3Mcbc4kkNAcIE13aIv5KoI2g+tHFFIZnFhtUilpYHc0VsMd4Fw7Jz81A8AB7L3wWu3OZB2CNiRnr1a3ew==} resolution: {integrity: sha512-vKbuG3Mcbc4kkNAcIE13aIv5KoI2g+tHFFIZnFhtUilpYHc0VsMd4Fw7Jz81A8AB7L3wWu3OZB2CNiRnr1a3ew==}
dev: false dev: false
/@next/env/12.0.11-canary.9: /@next/env/12.1.0:
resolution: {integrity: sha512-mdpsKiYCmYSSJQepsi8cJ6WDvsUYgeHLH4BhwMJhDGcQw7updHGKERZ8CgnaCMbU0q3yf9hl2n7oN9gv2U9E4w==} resolution: {integrity: sha512-nrIgY6t17FQ9xxwH3jj0a6EOiQ/WDHUos35Hghtr+SWN/ntHIQ7UpuvSi0vaLzZVHQWaDupKI+liO5vANcDeTQ==}
dev: false dev: false
/@next/eslint-plugin-next/12.0.11-canary.6: /@next/eslint-plugin-next/12.1.0:
resolution: {integrity: sha512-w5er84deDP+c6/8hKBXHXALqkEQr+dgXE0F75UIXK1otBKFb+agntqN5RfyXMWJUzpWU2+32kuFmBEDZAoHjUw==} resolution: {integrity: sha512-WFiyvSM2G5cQmh32t/SiQuJ+I2O+FHVlK/RFw5b1565O2kEM/36EXncjt88Pa+X5oSc+1SS+tWxowWJd1lqI+g==}
dependencies: dependencies:
glob: 7.1.7 glob: 7.1.7
dev: false dev: false
@ -3008,88 +3008,88 @@ packages:
'@mdx-js/react': 1.6.22_react@17.0.2 '@mdx-js/react': 1.6.22_react@17.0.2
dev: false dev: false
/@next/swc-android-arm64/12.0.11-canary.9: /@next/swc-android-arm64/12.1.0:
resolution: {integrity: sha512-9TYW6kRsNycWoP7zHp1FOdTvoupBEe4xlSiprDJQvRB19/Ms/6M1MvSJUhBLQ2DH5lExldF/jP2J6vALVkTuZg==} resolution: {integrity: sha512-/280MLdZe0W03stA69iL+v6I+J1ascrQ6FrXBlXGCsGzrfMaGr7fskMa0T5AhQIVQD4nA/46QQWxG//DYuFBcA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-arm64/12.0.11-canary.9: /@next/swc-darwin-arm64/12.1.0:
resolution: {integrity: sha512-AT7LGJMwWVBoftUiIHGzD8/HKF2mHxVRJrQJ293ibMK4RMnPK1iSNn1kI/5eKMw3jCPEG/yUGEZupP7NYYI2zA==} resolution: {integrity: sha512-R8vcXE2/iONJ1Unf5Ptqjk6LRW3bggH+8drNkkzH4FLEQkHtELhvcmJwkXcuipyQCsIakldAXhRbZmm3YN1vXg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-x64/12.0.11-canary.9: /@next/swc-darwin-x64/12.1.0:
resolution: {integrity: sha512-+MER8YoermGDL7pLSteYTnvW9zz7GriLcVotnMt3ODg4QGHk7bope4X7CdW9zmItvDC1f5VGCiKjBqg8TpEGNA==} resolution: {integrity: sha512-ieAz0/J0PhmbZBB8+EA/JGdhRHBogF8BWaeqR7hwveb6SYEIJaDNQy0I+ZN8gF8hLj63bEDxJAs/cEhdnTq+ug==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm-gnueabihf/12.0.11-canary.9: /@next/swc-linux-arm-gnueabihf/12.1.0:
resolution: {integrity: sha512-2x+j+pP7Iq6IjZ1mXxxfczVg2TEaQeZJL8HBBL+JxIi7f8l178El1uMg66SLIdr/k3H6mLp25X+Abl0NP33cew==} resolution: {integrity: sha512-njUd9hpl6o6A5d08dC0cKAgXKCzm5fFtgGe6i0eko8IAdtAPbtHxtpre3VeSxdZvuGFh+hb0REySQP9T1ttkog==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-gnu/12.0.11-canary.9: /@next/swc-linux-arm64-gnu/12.1.0:
resolution: {integrity: sha512-02HwAx0CFVmKNTDScLqvQfLEORhDguB6IXP1Gvht+2pDkFUWfWYDqba5tPaHOIUKCeWNRfJIRR3eFy/Pp1erPA==} resolution: {integrity: sha512-OqangJLkRxVxMhDtcb7Qn1xjzFA3s50EIxY7mljbSCLybU+sByPaWAHY4px97ieOlr2y4S0xdPKkQ3BCAwyo6Q==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-musl/12.0.11-canary.9: /@next/swc-linux-arm64-musl/12.1.0:
resolution: {integrity: sha512-IqRWieX1hs2ovLa4Jp0XYtEiHdYPljHpqONOOaU5BuJlZcLZUdojR17jvXMCm0jTvL1tXVAV2Dez6V/LYsFrTw==} resolution: {integrity: sha512-hB8cLSt4GdmOpcwRe2UzI5UWn6HHO/vLkr5OTuNvCJ5xGDwpPXelVkYW/0+C3g5axbDW2Tym4S+MQCkkH9QfWA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-gnu/12.0.11-canary.9: /@next/swc-linux-x64-gnu/12.1.0:
resolution: {integrity: sha512-J2c3fzkwmUeZgX8OtyjeM6qi4jzKZUk7L9fdDdiHhnzysUQk4Lsh7c4h2peYWwu9XAqZ52vSmeYwj2A/rmArbQ==} resolution: {integrity: sha512-OKO4R/digvrVuweSw/uBM4nSdyzsBV5EwkUeeG4KVpkIZEe64ZwRpnFB65bC6hGwxIBnTv5NMSnJ+0K/WmG78A==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-musl/12.0.11-canary.9: /@next/swc-linux-x64-musl/12.1.0:
resolution: {integrity: sha512-CmbURYTzg70pZIC1rQGNTSY2rJnblM6bIKBTYz3RcRgxhbTEZk3/YaE7D7ttALvJfJCRaAuGYjaOet52eqvOIg==} resolution: {integrity: sha512-JohhgAHZvOD3rQY7tlp7NlmvtvYHBYgY0x5ZCecUT6eCCcl9lv6iV3nfu82ErkxNk1H893fqH0FUpznZ/H3pSw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
dev: false dev: false
optional: true optional: true
/@next/swc-win32-arm64-msvc/12.0.11-canary.9: /@next/swc-win32-arm64-msvc/12.1.0:
resolution: {integrity: sha512-yGBW252SDphtlrsY+s4AvhfYE6S6Y2hsUbAiTQsAGugXFOwqXNPEoM2A5G2FG1yG/TUEIpreIWAB7dUO3E7/Hg==} resolution: {integrity: sha512-T/3gIE6QEfKIJ4dmJk75v9hhNiYZhQYAoYm4iVo1TgcsuaKLFa+zMPh4056AHiG6n9tn2UQ1CFE8EoybEsqsSw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
dev: false dev: false
optional: true optional: true
/@next/swc-win32-ia32-msvc/12.0.11-canary.9: /@next/swc-win32-ia32-msvc/12.1.0:
resolution: {integrity: sha512-E/eMBTfOKr3ya6S16jVKsSFR+UD/QuiwGyYx1qX0Jaq9pswx9gwi7rou4geU3sRdJXJqeBAQYFs6VfDLbMTIxw==} resolution: {integrity: sha512-iwnKgHJdqhIW19H9PRPM9j55V6RdcOo6rX+5imx832BCWzkDbyomWnlzBfr6ByUYfhohb8QuH4hSGEikpPqI0Q==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
dev: false dev: false
optional: true optional: true
/@next/swc-win32-x64-msvc/12.0.11-canary.9: /@next/swc-win32-x64-msvc/12.1.0:
resolution: {integrity: sha512-8+7AG45la2Yv6oDgCemuueNVOojbrDC1zXxdXfMIMkkj3JFHuaYR/T4Iphn09SOjg1TE7M/fzD9L1VCCYt8mQQ==} resolution: {integrity: sha512-aBvcbMwuanDH4EMrL2TthNJy+4nP59Bimn8egqv6GHMVj0a44cU6Au4PjOhLNqEh9l+IpRGBqMTzec94UdC5xg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -6048,8 +6048,8 @@ packages:
source-map: 0.6.1 source-map: 0.6.1
dev: false dev: false
/eslint-config-next/12.0.11-canary.6_473fdad0735444e3ca85f64cbcb6271e: /eslint-config-next/12.1.0_a833109067da92148152ff2f5707ab26:
resolution: {integrity: sha512-2OAar5wLCvHQdu8qQLV7i+aMcDfPWSJRhGfNH2gHPyF3H3+o0gowHv1ZffYahXPSOgS4vqdXlq+edNLcVYBFSA==} resolution: {integrity: sha512-tBhuUgoDITcdcM7xFvensi9I5WTI4dnvH4ETGRg1U8ZKpXrZsWQFdOKIDzR3RLP5HR3xXrLviaMM4c3zVoE/pA==}
peerDependencies: peerDependencies:
eslint: ^7.23.0 || ^8.0.0 eslint: ^7.23.0 || ^8.0.0
next: '>=10.2.0' next: '>=10.2.0'
@ -6058,7 +6058,7 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@next/eslint-plugin-next': 12.0.11-canary.6 '@next/eslint-plugin-next': 12.1.0
'@rushstack/eslint-patch': 1.1.0 '@rushstack/eslint-patch': 1.1.0
'@typescript-eslint/parser': 5.12.0_eslint@8.8.0+typescript@4.5.5 '@typescript-eslint/parser': 5.12.0_eslint@8.8.0+typescript@4.5.5
eslint: 8.8.0 eslint: 8.8.0
@ -6068,14 +6068,14 @@ packages:
eslint-plugin-jsx-a11y: 6.5.1_eslint@8.8.0 eslint-plugin-jsx-a11y: 6.5.1_eslint@8.8.0
eslint-plugin-react: 7.28.0_eslint@8.8.0 eslint-plugin-react: 7.28.0_eslint@8.8.0
eslint-plugin-react-hooks: 4.3.0_eslint@8.8.0 eslint-plugin-react-hooks: 4.3.0_eslint@8.8.0
next: 12.0.11-canary.9_react-dom@17.0.2+react@17.0.2 next: 12.1.0_react-dom@17.0.2+react@17.0.2
typescript: 4.5.5 typescript: 4.5.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: false dev: false
/eslint-config-next/12.0.11-canary.6_6a190f5566611a51beae5b8059e6f717: /eslint-config-next/12.1.0_eslint@8.8.0+next@12.1.0:
resolution: {integrity: sha512-2OAar5wLCvHQdu8qQLV7i+aMcDfPWSJRhGfNH2gHPyF3H3+o0gowHv1ZffYahXPSOgS4vqdXlq+edNLcVYBFSA==} resolution: {integrity: sha512-tBhuUgoDITcdcM7xFvensi9I5WTI4dnvH4ETGRg1U8ZKpXrZsWQFdOKIDzR3RLP5HR3xXrLviaMM4c3zVoE/pA==}
peerDependencies: peerDependencies:
eslint: ^7.23.0 || ^8.0.0 eslint: ^7.23.0 || ^8.0.0
next: '>=10.2.0' next: '>=10.2.0'
@ -6084,7 +6084,7 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@next/eslint-plugin-next': 12.0.11-canary.6 '@next/eslint-plugin-next': 12.1.0
'@rushstack/eslint-patch': 1.1.0 '@rushstack/eslint-patch': 1.1.0
'@typescript-eslint/parser': 5.12.0_eslint@8.8.0 '@typescript-eslint/parser': 5.12.0_eslint@8.8.0
eslint: 8.8.0 eslint: 8.8.0
@ -6094,7 +6094,7 @@ packages:
eslint-plugin-jsx-a11y: 6.5.1_eslint@8.8.0 eslint-plugin-jsx-a11y: 6.5.1_eslint@8.8.0
eslint-plugin-react: 7.28.0_eslint@8.8.0 eslint-plugin-react: 7.28.0_eslint@8.8.0
eslint-plugin-react-hooks: 4.3.0_eslint@8.8.0 eslint-plugin-react-hooks: 4.3.0_eslint@8.8.0
next: 12.0.11-canary.9_react-dom@17.0.2+react@17.0.2 next: 12.1.0_react-dom@17.0.2+react@17.0.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: false dev: false
@ -9304,13 +9304,13 @@ packages:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
dev: false dev: false
/next/12.0.11-canary.9_react-dom@17.0.2+react@17.0.2: /next/12.1.0_react-dom@17.0.2+react@17.0.2:
resolution: {integrity: sha512-lsUSsBiuf3QDKX5BCYr33Nv31kZDaHjbKXmi9S299LXdsv1S8z4cQQ/DHTziwSZ7Rtp25I0NzOmBmZAj8jvBkQ==} resolution: {integrity: sha512-s885kWvnIlxsUFHq9UGyIyLiuD0G3BUC/xrH0CEnH5lHEWkwQcHOORgbDF0hbrW9vr/7am4ETfX4A7M6DjrE7Q==}
engines: {node: '>=12.22.0'} engines: {node: '>=12.22.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
fibers: '>= 3.1.0' fibers: '>= 3.1.0'
node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 node-sass: ^6.0.0 || ^7.0.0
react: ^17.0.2 || ^18.0.0-0 react: ^17.0.2 || ^18.0.0-0
react-dom: ^17.0.2 || ^18.0.0-0 react-dom: ^17.0.2 || ^18.0.0-0
sass: ^1.3.0 sass: ^1.3.0
@ -9322,7 +9322,7 @@ packages:
sass: sass:
optional: true optional: true
dependencies: dependencies:
'@next/env': 12.0.11-canary.9 '@next/env': 12.1.0
caniuse-lite: 1.0.30001312 caniuse-lite: 1.0.30001312
postcss: 8.4.5 postcss: 8.4.5
react: 17.0.2 react: 17.0.2
@ -9330,17 +9330,17 @@ packages:
styled-jsx: 5.0.0_react@17.0.2 styled-jsx: 5.0.0_react@17.0.2
use-subscription: 1.5.1_react@17.0.2 use-subscription: 1.5.1_react@17.0.2
optionalDependencies: optionalDependencies:
'@next/swc-android-arm64': 12.0.11-canary.9 '@next/swc-android-arm64': 12.1.0
'@next/swc-darwin-arm64': 12.0.11-canary.9 '@next/swc-darwin-arm64': 12.1.0
'@next/swc-darwin-x64': 12.0.11-canary.9 '@next/swc-darwin-x64': 12.1.0
'@next/swc-linux-arm-gnueabihf': 12.0.11-canary.9 '@next/swc-linux-arm-gnueabihf': 12.1.0
'@next/swc-linux-arm64-gnu': 12.0.11-canary.9 '@next/swc-linux-arm64-gnu': 12.1.0
'@next/swc-linux-arm64-musl': 12.0.11-canary.9 '@next/swc-linux-arm64-musl': 12.1.0
'@next/swc-linux-x64-gnu': 12.0.11-canary.9 '@next/swc-linux-x64-gnu': 12.1.0
'@next/swc-linux-x64-musl': 12.0.11-canary.9 '@next/swc-linux-x64-musl': 12.1.0
'@next/swc-win32-arm64-msvc': 12.0.11-canary.9 '@next/swc-win32-arm64-msvc': 12.1.0
'@next/swc-win32-ia32-msvc': 12.0.11-canary.9 '@next/swc-win32-ia32-msvc': 12.1.0
'@next/swc-win32-x64-msvc': 12.0.11-canary.9 '@next/swc-win32-x64-msvc': 12.1.0
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
@ -13293,7 +13293,7 @@ packages:
dev: false dev: false
file:projects/demo.tgz_@mdx-js+react@1.6.22: file:projects/demo.tgz_@mdx-js+react@1.6.22:
resolution: {integrity: sha512-Np2YG0x2SP6d561O/JW305uxFsEbG59HfJrlEh/SoiZeB5S1prxUoYKbs3oVLEcnJ9RlzNpxDwqT4sBGIgu1sA==, tarball: file:projects/demo.tgz} resolution: {integrity: sha512-Rjd2nSA/9Gu41TKCWSKZM9bMVXqUFoWr30etKv07GGFP1NVkM8sWcH9bPezqiQxXRcZ5eMXet47PSS6Db8Qmqg==, tarball: file:projects/demo.tgz}
id: file:projects/demo.tgz id: file:projects/demo.tgz
name: '@rush-temp/demo' name: '@rush-temp/demo'
version: 0.0.0 version: 0.0.0
@ -13308,10 +13308,10 @@ packages:
'@types/react': 17.0.27 '@types/react': 17.0.27
'@yume-chan/async': 2.1.4 '@yume-chan/async': 2.1.4
eslint: 8.8.0 eslint: 8.8.0
eslint-config-next: 12.0.11-canary.6_473fdad0735444e3ca85f64cbcb6271e eslint-config-next: 12.1.0_a833109067da92148152ff2f5707ab26
mobx: 6.3.13 mobx: 6.3.13
mobx-react-lite: 3.2.3_96b0034b8b6bfac1b4cc60715db17f02 mobx-react-lite: 3.2.3_96b0034b8b6bfac1b4cc60715db17f02
next: 12.0.11-canary.9_react-dom@17.0.2+react@17.0.2 next: 12.1.0_react-dom@17.0.2+react@17.0.2
react: 17.0.2 react: 17.0.2
react-dom: 17.0.2_react@17.0.2 react-dom: 17.0.2_react@17.0.2
streamsaver: 2.0.6 streamsaver: 2.0.6

View file

@ -205,8 +205,8 @@ export class Adb {
return stdout; return stdout;
} }
public async install(apk: ReadableStream<ArrayBuffer>): Promise<void> { public install() {
return await install(this, apk); return install(this);
} }
public async sync(): Promise<AdbSync> { public async sync(): Promise<AdbSync> {

View file

@ -1,22 +1,32 @@
import { Adb } from "../adb"; import { Adb } from "../adb";
import { ReadableStream } from "../utils"; import { HookWritableStream, WritableStream } from "../utils";
import { escapeArg } from "./subprocess"; import { escapeArg } from "./subprocess";
import { AdbSync } from "./sync";
export async function install( export function install(
adb: Adb, adb: Adb,
apk: ReadableStream<ArrayBuffer>, ): WritableStream<ArrayBuffer> {
): Promise<void> {
const filename = `/data/local/tmp/${Math.random().toString().substring(2)}.apk`; const filename = `/data/local/tmp/${Math.random().toString().substring(2)}.apk`;
// Upload apk file to tmp folder return new HookWritableStream<ArrayBuffer, WritableStream<ArrayBuffer>, AdbSync>({
const sync = await adb.sync(); async start() {
const writable = sync.write(filename, undefined, undefined); // Upload apk file to tmp folder
await apk.pipeTo(writable); const sync = await adb.sync();
sync.dispose(); const writable = sync.write(filename, undefined, undefined);
// Invoke `pm install` to install it return {
await adb.subprocess.spawnAndWaitLegacy(['pm', 'install', escapeArg(filename)]); writable,
state: sync,
};
},
async close(sync) {
sync.dispose();
// Remove the temp file // Invoke `pm install` to install it
await adb.rm(filename); await adb.subprocess.spawnAndWaitLegacy(['pm', 'install', escapeArg(filename)]);
// Remove the temp file
await adb.rm(filename);
}
});
} }

View file

@ -3,7 +3,7 @@ import { Adb } from '../../adb';
import { AdbFeatures } from '../../features'; import { AdbFeatures } from '../../features';
import { AdbSocket } from '../../socket'; import { AdbSocket } from '../../socket';
import { AdbBufferedStream } from '../../stream'; import { AdbBufferedStream } from '../../stream';
import { AutoResetEvent, ReadableStream, TransformStream, WritableStream, WritableStreamDefaultWriter } from '../../utils'; import { AutoResetEvent, HookWritableStream, ReadableStream, TransformStream, WritableStream, WritableStreamDefaultWriter } from '../../utils';
import { AdbSyncEntryResponse, adbSyncOpenDir } from './list'; import { AdbSyncEntryResponse, adbSyncOpenDir } from './list';
import { adbSyncPull } from './pull'; import { adbSyncPull } from './pull';
import { adbSyncPush } from './push'; import { adbSyncPush } from './push';
@ -118,31 +118,29 @@ export class AdbSync extends AutoDisposable {
* *
* If the promise doesn't resolve immediately, it means the sync object is busy processing another command. * If the promise doesn't resolve immediately, it means the sync object is busy processing another command.
*/ */
public async write( public write(
filename: string, filename: string,
mode?: number, mode?: number,
mtime?: number, mtime?: number,
): Promise<WritableStream<ArrayBuffer>> { ): WritableStream<ArrayBuffer> {
await this.sendLock.wait(); return new HookWritableStream({
start: async () => {
const writable = adbSyncPush( await this.sendLock.wait();
this.stream, return {
this.writer, writable: adbSyncPush(
filename, this.stream,
mode, this.writer,
mtime, filename,
); mode,
mtime,
const lockStream = new TransformStream<ArrayBuffer, ArrayBuffer>(); ),
// `lockStream`'s `flush` will be invoked before `writable` fully closes, state: {},
// but `lockStream.readable.pipeTo` will wait for `writable` to close. };
lockStream.readable },
.pipeTo(writable) close: async () => {
.then(() => {
this.sendLock.notify(); this.sendLock.notify();
}); }
});
return lockStream.writable;
} }
public override async dispose() { public override async dispose() {

View file

@ -119,7 +119,7 @@ export class AdbSocketController extends AutoDisposable implements AdbSocketInfo
this.writeChunkLock.dispose(); this.writeChunkLock.dispose();
await this.dispatcher.sendPacket(AdbCommand.Close, this.localId, this.remoteId); await this.dispatcher.sendPacket(AdbCommand.Close, this.localId, this.remoteId);
this._passthrough.writable.close(); this._passthroughWriter.close();
this.writable.close(); this.writable.close();
} }
} }

View file

@ -334,7 +334,7 @@ try {
WritableStreamDefaultController, WritableStreamDefaultController,
WritableStreamDefaultWriter, WritableStreamDefaultWriter,
// @ts-expect-error // @ts-expect-error
} = await import('stream/web')); } = await import(/* webpackIgnore: true */ 'stream/web'));
} }
if (!(Symbol.asyncIterator in ReadableStream.prototype)) { if (!(Symbol.asyncIterator in ReadableStream.prototype)) {

View file

@ -1,6 +1,7 @@
import Struct, { decodeUtf8, StructLike, StructValueType } from "@yume-chan/struct"; import Struct, { decodeUtf8, StructLike, StructValueType } from "@yume-chan/struct";
import { BufferedStream, BufferedStreamEndedError } from "../stream"; import { BufferedStream, BufferedStreamEndedError } from "../stream";
import { TransformStream } from "./stream"; import { chunkArrayLike } from "./chunk";
import { TransformStream, WritableStream, WritableStreamDefaultWriter } from "./stream";
export class DecodeUtf8Stream extends TransformStream<ArrayBuffer, string>{ export class DecodeUtf8Stream extends TransformStream<ArrayBuffer, string>{
public constructor() { public constructor() {
@ -74,3 +75,51 @@ export class StructSerializeStream<T extends Struct<any, any, any, any>>
}); });
} }
} }
export interface WritableStreamHooks<T, W extends WritableStream<T>, S> {
start(): Promise<{ writable: W, state: S; }>;
close(state: S): Promise<void>;
}
export class HookWritableStream<T, W extends WritableStream<T>, S> extends WritableStream<T>{
public writable!: W;
private writer!: WritableStreamDefaultWriter<T>;
private state!: S;
public constructor(hooks: WritableStreamHooks<T, W, S>) {
super({
start: async () => {
const { writable, state } = await hooks.start();
this.writable = writable;
this.writer = writable.getWriter();
this.state = state;
},
write: async (chunk) => {
await this.writer.ready;
await this.writer.write(chunk);
},
abort: async (reason) => {
await this.writer.abort(reason);
hooks.close(this.state);
},
close: async () => {
await this.writer.close();
await hooks.close(this.state);
},
});
}
}
export class ChunkStream extends TransformStream<ArrayBuffer, ArrayBuffer>{
public constructor(size: number) {
super({
transform(chunk, controller) {
for (const piece of chunkArrayLike(chunk, size)) {
controller.enqueue(piece);
}
}
});
}
}

View file

@ -1,4 +1,4 @@
import { Adb, TransformStream } from "@yume-chan/adb"; import { Adb, AdbSync, WritableStream, WritableStreamDefaultWriter } from "@yume-chan/adb";
import { DEFAULT_SERVER_PATH } from "./options"; import { DEFAULT_SERVER_PATH } from "./options";
export interface PushServerOptions { export interface PushServerOptions {
@ -9,21 +9,28 @@ export async function pushServerStream(
device: Adb, device: Adb,
options: PushServerOptions = {} options: PushServerOptions = {}
) { ) {
const { const { path = DEFAULT_SERVER_PATH } = options;
path = DEFAULT_SERVER_PATH,
} = options;
const sync = await device.sync(); let sync!: AdbSync;
const writable = sync.write(path); let writable!: WritableStream<ArrayBuffer>;
let writer!: WritableStreamDefaultWriter<ArrayBuffer>;
const lockStream = new TransformStream<ArrayBuffer, ArrayBuffer>(); return new WritableStream<ArrayBuffer>({
// Same as inside AdbSync, async start() {
// can only use `pipeTo` to detect the writable is fully closed. sync = await device.sync();
lockStream.readable writable = sync.write(path);
.pipeTo(writable) writer = writable.getWriter();
.then(() => { },
async write(chunk: ArrayBuffer) {
await writer.ready;
await writer.write(chunk);
},
async abort(e) {
await writer.abort(e);
sync.dispose(); sync.dispose();
}); },
async close() {
return lockStream.writable; await writer.close();
await sync.dispose();
},
});
} }