attempt to use part/copy idea.
This commit is contained in:
parent
f0a722b3fb
commit
123933d459
@ -3,5 +3,5 @@
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
|
||||
<application android:largeHeap="true" android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
|
||||
</manifest>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true">
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:largeHeap="true">
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||
|
61
app/lib/readstream.ts
Normal file
61
app/lib/readstream.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/* eslint-disable unicorn/no-null */
|
||||
import * as fs from 'expo-file-system';
|
||||
import { Readable } from 'readable-stream';
|
||||
|
||||
class ExpoReadStream extends Readable {
|
||||
private readonly fileUri: string;
|
||||
private fileSize: number;
|
||||
private currentPosition: number;
|
||||
private readonly chunkSize: number;
|
||||
|
||||
constructor(fileUri: string, options: fs.ReadingOptions) {
|
||||
super();
|
||||
this.fileUri = fileUri;
|
||||
this.fileSize = 0; // Initialize file size (could be fetched if necessary)
|
||||
this.currentPosition = options.position ?? 0;
|
||||
/**
|
||||
* Default chunk size in bytes. React Native Expo will OOM at 110MB, so we set this to 1/100 of it to balance speed and memory usage and importantly the feedback for user.
|
||||
* If this is too large, the progress bar will be stuck when down stream processing this chunk.
|
||||
*/
|
||||
this.chunkSize = options.length ?? 1024 * 1024;
|
||||
void this._init();
|
||||
}
|
||||
|
||||
async _init() {
|
||||
try {
|
||||
const fileInfo = await fs.getInfoAsync(this.fileUri, { size: true });
|
||||
if (fileInfo.exists) {
|
||||
this.fileSize = fileInfo.size ?? 0;
|
||||
} else {
|
||||
this.fileSize = 0;
|
||||
}
|
||||
} catch (error) {
|
||||
this.emit('error', error);
|
||||
}
|
||||
}
|
||||
|
||||
_read() {
|
||||
const readingOptions = {
|
||||
encoding: fs.EncodingType.Base64,
|
||||
position: this.currentPosition,
|
||||
length: this.chunkSize,
|
||||
} satisfies fs.ReadingOptions;
|
||||
fs.readAsStringAsync(this.fileUri, readingOptions).then(chunk => {
|
||||
if (chunk.length === 0) {
|
||||
// End of the stream
|
||||
this.emit('progress', 1);
|
||||
this.push(null);
|
||||
} else {
|
||||
this.currentPosition = Math.min(this.chunkSize + this.currentPosition, this.fileSize);
|
||||
this.emit('progress', this.fileSize === 0 ? 0.5 : (this.currentPosition / this.fileSize));
|
||||
this.push(Buffer.from(chunk, 'base64'));
|
||||
}
|
||||
}).catch(error => {
|
||||
this.emit('error', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function createReadStream(fileUri: string, options: { encoding?: fs.EncodingType; end?: number; highWaterMark?: number; start?: number } = {}): ExpoReadStream {
|
||||
return new ExpoReadStream(fileUri, options);
|
||||
}
|
@ -4,6 +4,7 @@ import { File, Paths } from "expo-file-system/next";
|
||||
import { getDb } from "./db";
|
||||
import * as Crypto from "expo-crypto";
|
||||
import { arrbufToStr, strToArrBuf } from "./util";
|
||||
import { createReadStream } from "./readstream";
|
||||
|
||||
export const WHISPER_MODEL_PATH = Paths.join(
|
||||
FileSystem.documentDirectory || "file:///",
|
||||
@ -119,6 +120,7 @@ export class WhisperFile {
|
||||
|
||||
target_hash: string | undefined;
|
||||
does_target_exist: boolean = false;
|
||||
does_part_target_exist: boolean = false;
|
||||
download_data: FileSystem.DownloadProgressData | undefined;
|
||||
|
||||
constructor(
|
||||
@ -136,6 +138,10 @@ export class WhisperFile {
|
||||
return Paths.join(WHISPER_MODEL_PATH, this.targetFileName as string);
|
||||
}
|
||||
|
||||
get targetPartPath () {
|
||||
return this.targetPath + ".part";
|
||||
}
|
||||
|
||||
get targetFile() {
|
||||
return new File(this.targetPath);
|
||||
}
|
||||
@ -144,8 +150,13 @@ export class WhisperFile {
|
||||
return await FileSystem.getInfoAsync(this.targetPath);
|
||||
}
|
||||
|
||||
async getTargetPartInfo() {
|
||||
return await FileSystem.getInfoAsync(this.targetPartPath);
|
||||
}
|
||||
|
||||
async updateTargetExistence() {
|
||||
this.does_target_exist = (await this.getTargetInfo()).exists;
|
||||
this.does_part_target_exist = (await this.getTargetPartInfo()).exists;
|
||||
}
|
||||
|
||||
public async getTargetSha() {
|
||||
@ -160,6 +171,7 @@ export class WhisperFile {
|
||||
});
|
||||
const data = strToArrBuf(strData);
|
||||
|
||||
|
||||
const digest = await Crypto.digest(
|
||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||
data
|
||||
@ -246,8 +258,10 @@ export class WhisperFile {
|
||||
async createDownloadResumable(
|
||||
options: {
|
||||
onData?: DownloadCallback | undefined;
|
||||
onComplete?: CompletionCallback | undefined;
|
||||
} = {
|
||||
onData: undefined,
|
||||
onComplete: undefined,
|
||||
}
|
||||
) {
|
||||
await this.syncHfMetadata();
|
||||
@ -263,6 +277,10 @@ export class WhisperFile {
|
||||
// If it exists, load the existing data.
|
||||
await this.updateTargetExistence();
|
||||
|
||||
if (this.does_part_target_exist) {
|
||||
options.onComplete && options.onComplete(this)
|
||||
}
|
||||
|
||||
try {
|
||||
const existingData = this.does_target_exist
|
||||
? await FileSystem.readAsStringAsync(this.targetPath, {
|
||||
@ -289,11 +307,11 @@ export class WhisperFile {
|
||||
console.error("Failed to update HuggingFace metadata: %s", err)
|
||||
}
|
||||
|
||||
try {
|
||||
await this.updateTargetHash();
|
||||
} catch (er) {
|
||||
console.error("Failed to update target hash: %s", er);
|
||||
}
|
||||
// try {
|
||||
// await this.updateTargetHash();
|
||||
// } catch (er) {
|
||||
// console.error("Failed to update target hash: %s", er);
|
||||
// }
|
||||
|
||||
try {
|
||||
await this.updateTargetExistence();
|
||||
@ -301,6 +319,16 @@ export class WhisperFile {
|
||||
console.error("Failed to update target existence: %s", err)
|
||||
}
|
||||
if (options.onData) await options.onData(this);
|
||||
|
||||
if (data.totalBytesExpectedToWrite === 0) {
|
||||
console.debug("Finalizing; copying from %s -> %s", this.targetPartPath, this.targetPath);
|
||||
await FileSystem.copyAsync({
|
||||
from: this.targetPartPath,
|
||||
to: this.targetPath,
|
||||
});
|
||||
await this.updateTargetExistence();
|
||||
options.onComplete && options.onComplete(this);
|
||||
}
|
||||
},
|
||||
existingData ? existingData : undefined
|
||||
);
|
||||
@ -311,6 +339,7 @@ export class WhisperFile {
|
||||
}
|
||||
|
||||
export type DownloadCallback = (arg0: WhisperFile) => any;
|
||||
export type CompletionCallback = (arg0: WhisperFile) => any;
|
||||
|
||||
export const WHISPER_FILES = {
|
||||
small: new WhisperFile("small"),
|
||||
|
@ -110,6 +110,10 @@ const SettingsComponent = () => {
|
||||
setBytesRemaining(arg0.download_data?.totalBytesExpectedToWrite);
|
||||
};
|
||||
|
||||
const doOnComplete = (arg0: WhisperFile) => {
|
||||
setWhisperFile(arg0);
|
||||
}
|
||||
|
||||
const doDownload = async () => {
|
||||
if (!whisperModel) {
|
||||
throw new Error("Could not start download because whisperModel not set.");
|
||||
@ -122,8 +126,10 @@ const SettingsComponent = () => {
|
||||
try {
|
||||
const resumable = await whisperFile.createDownloadResumable({
|
||||
onData: doSetDownloadStatus,
|
||||
onComplete: doOnComplete,
|
||||
});
|
||||
setDownloader(resumable);
|
||||
if (!resumable) throw new Error("Could not construct resumable");
|
||||
await resumable.resumeAsync();
|
||||
} catch (error) {
|
||||
console.error("Failed to download whisper model:", error);
|
||||
@ -193,7 +199,7 @@ const SettingsComponent = () => {
|
||||
<Text style={styles.buttonText}>DELETE {whisperModel.toUpperCase()}</Text>
|
||||
</Pressable>))
|
||||
}
|
||||
<Pressable onPress={doDownload} style={styles.pauseDownloadButton}>
|
||||
<Pressable onPress={doDownload} style={styles.downloadButton}>
|
||||
<Text style={styles.buttonText}>DOWNLOAD {whisperModel.toUpperCase()}</Text>
|
||||
</Pressable>
|
||||
))
|
||||
@ -205,7 +211,7 @@ const SettingsComponent = () => {
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
{bytesDone && bytesRemaining && (
|
||||
{bytesDone && bytesRemaining && whisperFile?.does_part_target_exist && (
|
||||
<View>
|
||||
{whisperFile &&
|
||||
(<Text>
|
||||
|
117
package-lock.json
generated
117
package-lock.json
generated
@ -50,6 +50,7 @@
|
||||
"react-native-sqlite-storage": "^6.0.1",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-webview": "13.12.5",
|
||||
"readable-stream": "^4.7.0",
|
||||
"sqlite": "^5.1.1",
|
||||
"sqlite3": "^5.1.7",
|
||||
"whisper.rn": "^0.3.9"
|
||||
@ -66,6 +67,7 @@
|
||||
"@types/react-native-sqlite-storage": "^6.0.5",
|
||||
"@types/react-navigation": "^3.0.8",
|
||||
"@types/react-test-renderer": "^18.3.1",
|
||||
"@types/readable-stream": "^4.0.18",
|
||||
"babel-jest": "^29.7.0",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"expo": "~52.0.28",
|
||||
@ -5047,6 +5049,24 @@
|
||||
"@types/react": "^18"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/readable-stream": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.18.tgz",
|
||||
"integrity": "sha512-21jK/1j+Wg+7jVw1xnSwy/2Q1VgVjWuFssbYGTREPUBeZ+rqVFl2udq0IkxzPC0ZhOzVceUbyIACFZKLqKEBlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"safe-buffer": "~5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/readable-stream/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/stack-utils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
|
||||
@ -5590,6 +5610,21 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/are-we-there-yet/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
@ -6031,6 +6066,20 @@
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
@ -15216,17 +15265,43 @@
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
|
||||
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
"abort-controller": "^3.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"events": "^3.3.0",
|
||||
"process": "^0.11.10",
|
||||
"string_decoder": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readline": {
|
||||
@ -16451,6 +16526,20 @@
|
||||
"readable-stream": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-browserify/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-buffers": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz",
|
||||
@ -16849,6 +16938,20 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
|
@ -24,6 +24,7 @@
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/native-stack": "^7.2.0",
|
||||
"expo": "~52.0.28",
|
||||
"expo-audio": "~0.3.4",
|
||||
"expo-background-fetch": "~13.0.5",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-constants": "~17.0.6",
|
||||
@ -56,10 +57,10 @@
|
||||
"react-native-sqlite-storage": "^6.0.1",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-webview": "13.12.5",
|
||||
"readable-stream": "^4.7.0",
|
||||
"sqlite": "^5.1.1",
|
||||
"sqlite3": "^5.1.7",
|
||||
"whisper.rn": "^0.3.9",
|
||||
"expo-audio": "~0.3.4"
|
||||
"whisper.rn": "^0.3.9"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo",
|
||||
@ -86,6 +87,7 @@
|
||||
"@types/react-native-sqlite-storage": "^6.0.5",
|
||||
"@types/react-navigation": "^3.0.8",
|
||||
"@types/react-test-renderer": "^18.3.1",
|
||||
"@types/readable-stream": "^4.0.18",
|
||||
"babel-jest": "^29.7.0",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"expo": "~52.0.28",
|
||||
|
Loading…
x
Reference in New Issue
Block a user