[RNE Rewrite] Integrate resource fetcher (react-native-blob-util) - #1328
[RNE Rewrite] Integrate resource fetcher (react-native-blob-util)#1328msluszniak wants to merge 5 commits into
Conversation
Replace the temporary react-native-fs download hook with a single
blob-util-backed fetcher living in react-native-executorch (no separate
expo/bare packages).
- src/fetcher: imperative `download(source | source[], { onProgress, signal })`
returning local path(s); persistent DocumentDir cache keyed by URL hash.
- HTTP-Range auto-resume of interrupted downloads via a .partial file, with a
safe fallback to a fresh full download if partial assembly fails.
- Byte-weighted unified progress across multiple files; AbortSignal cancel that
preserves bytes for later resume.
- Bundled download telemetry (HF download counter + anonymous download event),
fired once per genuine, non-cached fetch.
- Rewire useResourceDownload + inspectModel onto the new fetcher; swap the
react-native-fs dependency for react-native-blob-util in the lib and example
apps.
Closes #1253
…tories Every create<Task> factory now runs its model/tokenizer path fields through the fetcher's download() before loading, so remote URLs are transparently downloaded and cached. Because download() passes local paths through untouched, the use<Task> hooks (which still pre-download for progress) hit an instant no-op — no double download, single telemetry event. - Wrap each loadModel/loadTokenizer arg with download() across all 12 factories (single modelPath, plus tokenizerPath and nested vadModel for the multi-file tasks). Whisper delegates VAD resolution to the FSMN factory. - Add a tuple-preserving download() overload so download([a, b]) resolves to [string, string] under noUncheckedIndexedAccess. - Imperative create<Task>(models.X) now works without an explicit download().
…large downloads On-device testing revealed react-native-blob-util's in-process streaming download is broken on modern Android (a 0.24.10 regression, upstream #475): it aborts after 8 KB with "Download interrupted", so every model download failed. curl and the system DownloadManager fetch the same URLs fine. Split the fetcher backend by platform: - Android: route through blob-util's system DownloadManager. It reliably handles files >2 GB (the whole point — LLM .pte files exceed the OkHttp 2 GB in-process limit), continues in the background / across app kill, and resumes transient network drops itself. Downloads stage in the app-private external files dir so DownloadManager can write there and the move into the cache stays on one volume (no multi-GB cross-filesystem copy). - iOS: keep the NSURLSession streaming path with .partial/HTTP-Range resume (unaffected by the Android regression, no 2 GB limit). Verified on a physical Android device: byte-weighted progress, cache-hit short-circuit, and create<Task>(models.X) URL resolution all pass end to end.
Analytics stay enabled by default; setTelemetryEnabled(false) opts out of the anonymous download-event POST to Software Mansion. The Hugging Face download counter is unaffected and always fires.
| // Throwaway download to a temp path — inspection shouldn't populate the | ||
| // persistent resource cache, so we don't go through `download()`. |
There was a problem hiding this comment.
Since this will now become part of the standard API we should add an explanation of this throwaway behavior to the jsdoc.
| // Set by the native layer when running on a simulator/emulator, so dev traffic | ||
| // can be filtered out server-side. Absent (⇒ false) until then. | ||
| function isEmulator(): boolean { | ||
| return (globalThis as { __rne_isEmulator?: boolean }).__rne_isEmulator === true; |
There was a problem hiding this comment.
How does this work? Where is the property __rne_isEmulator set?
There was a problem hiding this comment.
Let's rename this file to fetcher.ts so that all files have consistent naming.
| sources: T, | ||
| options?: DownloadOptions | ||
| ): Promise<{ [K in keyof T]: string }>; | ||
| export async function download( |
There was a problem hiding this comment.
I'm not sure that the pattern of adding download(modelPath) to every task implementation is the best way to achieve that. I think we should separate the downloading from creating the pipeline, so imo the pipelines should stay as they were and the pattern should rather be something like
const model = download(models.classification.EFFICIENTNET_V2_S);
const { classify, dispose } = createClassifier(model);where download would work in the following way: it takes an arbitrary nested object and for each string leaf attribute it checks if it's a valid url and if so downloads it and substitutes the url for the local path. This way we wouldn't need two different mechanisms for downloading in create<Task> and use<Task> (where the path substitution is left as it was) and we wouldn't have to actually write the path substitution by hand in each hook. The only problem I see is that I'm not sure how difficult would it be to implement something like this in a way that download also returns a proper type but is generic at the same time (so it works for all model types out of the box). My gut feeling is that it should be possible as TS type system is quite powerful but I don't have a concrete idea right now.
Description
Adds resource fetching to the rewrite as a single
react-native-blob-util-backed fetcher insidereact-native-executorch(no separate expo/bare packages). Publicdownload(source | source[], { onProgress, signal })returns local path(s); local paths pass through untouched. Everycreate<Task>factory now resolves remote model URLs internally, socreate<Task>(models.X)works directly and theuse<Task>hooks (which pre-download for progress) hit an instant no-op. HF download-counter + download-event telemetry are bundled and fired once per genuine fetch. Replaces the temporaryreact-native-fshook.Backend is split by platform: Android uses the system DownloadManager (reliable for files >2 GB, continues in the background, OS-managed resume), iOS streams via NSURLSession with
.partial/HTTP-Range resume.Introduces a breaking change?
Type of change
Tested on
Testing instructions
computer-visionexample app.createClassifier(models.classification.EFFICIENTNET_V2_S.XNNPACK_FP32)resolves the URL to a cached path internally.Screenshots
Related issues
Closes #1253
Checklist
Additional notes
react-native-fspeer dependency forreact-native-blob-util(also added to the example apps).setTelemetryEnabled(false)(enabled by default); the Hugging Face download counter always fires.LIB_VERSIONis hardcoded pending [RNE Rewrite] clang-tidy CI - drop RNET_BASE_URL test-release override once a real versioned release exists #1291.