detemine root cause of download issue.

This commit is contained in:
Jordan 2025-02-28 07:13:45 -08:00
parent 87446784ae
commit 4549442bd8
4 changed files with 104 additions and 44 deletions

View File

@ -136,12 +136,32 @@ export function whisperFileExists(whisper_model : whisper_model_tag_t) {
return target.exists
}
export async function downloadWhisperModel(
export type DownloadCallback = (arg0 : FileSystem.DownloadProgressData) => any;
async function updateModelSize(model_label : string, size : number) {
const db = await getDb();
const query = "INSERT OR REPLACE INTO whisper_models (model, bytes_total) VALUES (?, ?)"
const stmt = db.prepareSync(query);
stmt.executeSync(model_label, size);
}
async function getExpectedModelSize(model_label : string) : Promise<number | undefined> {
const db = await getDb();
const query = "SELECT bytes_total FROM whisper_models WHERE model = ?"
const stmt = db.prepareSync(query);
const curs = stmt.executeSync(model_label);
const row = curs.getFirstSync()
return row ? row.bytes_total : undefined;
}
export async function initiateWhisperDownload(
whisper_model: whisper_model_tag_t,
options: {
force_redownload: boolean;
force_redownload?: boolean;
onDownload?: DownloadCallback | undefined;
} = {
force_redownload: false,
onDownload: undefined,
}
) {
@ -161,34 +181,33 @@ export async function downloadWhisperModel(
if (options.force_redownload) {
whisperTarget.delete()
} else {
console.warn("Whisper model for %s already exists", whisper_model);
return;
const expected = await getExpectedModelSize(whisper_model);
if (whisperTarget.size === expected) {
console.warn("Whisper model for %s already exists", whisper_model);
return undefined;
}
}
}
// Initiate a new resumable download.
const spec = WHISPER_MODELS[whisper_model];
console.log("Downloading %s", spec.source);
const resumable = FileSystem.createDownloadResumable(
spec.source,
whisperTarget.uri,
undefined,
{},
// On each data write, update the whisper model download status.
// Note that since createDownloadResumable callback only works in the foreground,
// a background process will also be updating the file size.
async (data) => {
const db = await getDb();
const args = [
whisper_model,
data.totalBytesWritten,
data.totalBytesExpectedToWrite,
];
console.log("%s, %s of %s", whisper_model, data.totalBytesWritten, data.totalBytesExpectedToWrite);
await db.runAsync(
`INSERT OR REPLACE INTO whisper_models (model, bytes_done, bytes_remaining) VALUES (?, ?, ?)`,
args
);
}
console.log("%s: %d bytes of %d", whisperTarget.uri, data.totalBytesWritten, data.totalBytesExpectedToWrite);
await updateModelSize(whisper_model, data.totalBytesExpectedToWrite)
if (options.onDownload) await options.onDownload(data);
},
whisperTarget.exists ? whisperTarget.base64() : undefined,
);
await resumable.downloadAsync();
return resumable;
}

View File

@ -6,11 +6,12 @@ import { Settings } from "@/app/lib/settings";
import { LanguageServer, fetchWithTimeout } from "@/app/i18n/api";
import { Picker } from "@react-native-picker/picker";
import { longLang } from "@/app/i18n/lang";
import FileSystem, { DownloadResumable } from "expo-file-system";
import { LIBRETRANSLATE_BASE_URL } from "@/constants/api";
import {
WHISPER_MODELS,
WHISPER_MODEL_DIR,
downloadWhisperModel,
initiateWhisperDownload,
download_status,
getWhisperDownloadStatus,
getWhisperTarget,
@ -53,6 +54,10 @@ const SettingsComponent: React.FC = () => {
const [langServerConn, setLangServerConn] = useState<
undefined | connection_test_t
>();
const [whisperDownloadProgress, setWhisperDownloadProgress] = useState<
FileSystem.DownloadProgressData | undefined
>();
const [downloader, setDownloader] = useState<DownloadResumable | undefined>();
const fillHostLanguageOptions = async () => {
const settings = await Settings.getDefault();
@ -73,7 +78,7 @@ const SettingsComponent: React.FC = () => {
error: `Could not connect to ${libretranslateBaseUrl}: ${err}`,
});
}
}
};
useEffect(() => {
(async () => {
@ -147,12 +152,25 @@ const SettingsComponent: React.FC = () => {
const doReadownload = async () => {
if (!whisperModel) return;
await downloadWhisperModel(whisperModel, { force_redownload: true });
await initiateWhisperDownload(whisperModel, {
force_redownload: true,
onDownload: setWhisperDownloadProgress,
});
};
const doDownload = async () => {
if (!whisperModel) return;
await downloadWhisperModel(whisperModel);
try {
setDownloader(
await initiateWhisperDownload(whisperModel, {
onDownload: setWhisperDownloadProgress,
})
);
await downloader?.downloadAsync();
console.log("completed download");
} catch (err) {
console.error(err);
}
};
const handleHostLanguageChange = async (value: string) => {
@ -217,28 +235,18 @@ const SettingsComponent: React.FC = () => {
<Picker.Item key={key} label={label} value={key} />
))}
</Picker>
{whisperModel && (
<View>
{downloadStatus?.status === "complete" ? (
<Pressable onPress={doReadownload}>Re-Download</Pressable>
) : downloadStatus?.status === "in_progress" ? (
<Text>
{(downloadStatus.bytes.done / downloadStatus.bytes.total) * 100.0}{" "}
% complete
{downloadStatus.bytes.done} bytes of {downloadStatus.bytes.total}
</Text>
) : (
<View>
<Pressable onPress={doDownload} style={styles.button}>
<Text style={styles.buttonText}>Download</Text>
</Pressable>
<Text>
This will download to {Paths.join(WHISPER_MODEL_DIR, WHISPER_MODELS[whisperModel].target)}
</Text>
</View>
)}
</View>
)}
<View>
{ /* If there's a downloader, that means we're in the middle of a download */}
{downloader && whisperDownloadProgress && (
<Text>
{whisperDownloadProgress.totalBytesWritten} of {whisperDownloadProgress.totalBytesExpectedToWrite}
</Text>
)
}
<Pressable onPress={doDownload} style={styles.button}>
<Text style={styles.buttonText}>Download</Text>
</Pressable>
</View>
</View>
) : (
<View>

View File

View File

@ -0,0 +1,33 @@
import { Settings } from "@/app/lib/settings"
import { WHISPER_MODELS } from "@/app/lib/whisper"
import { Picker } from "@react-native-picker/picker"
import { useEffect, useState } from "react"
import { Pressable, View } from "react-native"
const WhisperDownloader = () => {
const [whisperModel, setWhisperModel] = useState<string|undefined>();
useEffect(() => {
const settings = await Settings.getDefault();
setWhisperModel((await settings.getWhisperModel()) || "small");
}, [])
return (
<View>
<Picker
selectedValue={whisperModel || ""}
style={{ height: 50, width: "100%" }}
onValueChange={setWhisperModel}
accessibilityHint="whisper model"
>
{Object.entries(WHISPER_MODELS).map(([key, { label }]) => (
<Picker.Item key={key} label={label} value={key} />
))}
</Picker>
<WhisperDownloadButton whisperModel={whisperModel} />
<WhisperDownloadInfo whisperModel={whisperModel} />
</View>
}